var H5P = H5P || {}; /** * Transition contains helper function relevant for transitioning */ H5P.Transition = (function ($) { /** * @class * @namespace H5P */ Transition = {}; /** * @private */ Transition.transitionEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'transition': 'transitionend', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd' }; /** * @private */ Transition.cache = []; /** * Get the vendor property name for an event * * @function H5P.Transition.getVendorPropertyName * @static * @private * @param {string} prop Generic property name * @return {string} Vendor specific property name */ Transition.getVendorPropertyName = function (prop) { if (Transition.cache[prop] !== undefined) { return Transition.cache[prop]; } var div = document.createElement('div'); // Handle unprefixed versions (FF16+, for example) if (prop in div.style) { Transition.cache[prop] = prop; } else { var prefixes = ['Moz', 'Webkit', 'O', 'ms']; var prop_ = prop.charAt(0).toUpperCase() + prop.substr(1); if (prop in div.style) { Transition.cache[prop] = prop; } else { for (var i = 0; i < prefixes.length; ++i) { var vendorProp = prefixes[i] + prop_; if (vendorProp in div.style) { Transition.cache[prop] = vendorProp; break; } } } } return Transition.cache[prop]; }; /** * Get the name of the transition end event * * @static * @private * @return {string} description */ Transition.getTransitionEndEventName = function () { return Transition.transitionEndEventNames[Transition.getVendorPropertyName('transition')] || undefined; }; /** * Helper function for listening on transition end events * * @function H5P.Transition.onTransitionEnd * @static * @param {domElement} $element The element which is transitioned * @param {function} callback The callback to be invoked when transition is finished * @param {number} timeout Timeout in milliseconds. Fallback if transition event is never fired */ Transition.onTransitionEnd = function ($element, callback, timeout) { // Fallback on 1 second if transition event is not supported/triggered timeout = timeout || 1000; Transition.transitionEndEventName = Transition.transitionEndEventName || Transition.getTransitionEndEventName(); var callbackCalled = false; var doCallback = function () { if (callbackCalled) { return; } $element.off(Transition.transitionEndEventName, callback); callbackCalled = true; clearTimeout(timer); callback(); }; var timer = setTimeout(function () { doCallback(); }, timeout); $element.on(Transition.transitionEndEventName, function () { doCallback(); }); }; /** * Wait for a transition - when finished, invokes next in line * * @private * * @param {Object[]} transitions Array of transitions * @param {H5P.jQuery} transitions[].$element Dom element transition is performed on * @param {number=} transitions[].timeout Timeout fallback if transition end never is triggered * @param {bool=} transitions[].break If true, sequence breaks after this transition * @param {number} index The index for current transition */ var runSequence = function (transitions, index) { if (index >= transitions.length) { return; } var transition = transitions[index]; H5P.Transition.onTransitionEnd(transition.$element, function () { if (transition.end) { transition.end(); } if (transition.break !== true) { runSequence(transitions, index+1); } }, transition.timeout || undefined); }; /** * Run a sequence of transitions * * @function H5P.Transition.sequence * @static * @param {Object[]} transitions Array of transitions * @param {H5P.jQuery} transitions[].$element Dom element transition is performed on * @param {number=} transitions[].timeout Timeout fallback if transition end never is triggered * @param {bool=} transitions[].break If true, sequence breaks after this transition */ Transition.sequence = function (transitions) { runSequence(transitions, 0); }; return Transition; })(H5P.jQuery); ; var H5P = H5P || {}; /** * Class responsible for creating a help text dialog */ H5P.JoubelHelpTextDialog = (function ($) { var numInstances = 0; /** * Display a pop-up containing a message. * * @param {H5P.jQuery} $container The container which message dialog will be appended to * @param {string} message The message * @param {string} closeButtonTitle The title for the close button * @return {H5P.jQuery} */ function JoubelHelpTextDialog(header, message, closeButtonTitle) { H5P.EventDispatcher.call(this); var self = this; numInstances++; var headerId = 'joubel-help-text-header-' + numInstances; var helpTextId = 'joubel-help-text-body-' + numInstances; var $helpTextDialogBox = $('
', { 'class': 'joubel-help-text-dialog-box', 'role': 'dialog', 'aria-labelledby': headerId, 'aria-describedby': helpTextId }); $('
', { 'class': 'joubel-help-text-dialog-background' }).appendTo($helpTextDialogBox); var $helpTextDialogContainer = $('
', { 'class': 'joubel-help-text-dialog-container' }).appendTo($helpTextDialogBox); $('
', { 'class': 'joubel-help-text-header', 'id': headerId, 'role': 'header', 'html': header }).appendTo($helpTextDialogContainer); $('
', { 'class': 'joubel-help-text-body', 'id': helpTextId, 'html': message, 'role': 'document', 'tabindex': 0 }).appendTo($helpTextDialogContainer); var handleClose = function () { $helpTextDialogBox.remove(); self.trigger('closed'); }; var $closeButton = $('
', { 'class': 'joubel-help-text-remove', 'role': 'button', 'title': closeButtonTitle, 'tabindex': 1, 'click': handleClose, 'keydown': function (event) { // 32 - space, 13 - enter if ([32, 13].indexOf(event.which) !== -1) { event.preventDefault(); handleClose(); } } }).appendTo($helpTextDialogContainer); /** * Get the DOM element * @return {HTMLElement} */ self.getElement = function () { return $helpTextDialogBox; }; self.focus = function () { $closeButton.focus(); }; } JoubelHelpTextDialog.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelHelpTextDialog.prototype.constructor = JoubelHelpTextDialog; return JoubelHelpTextDialog; }(H5P.jQuery)); ; var H5P = H5P || {}; /** * Class responsible for creating auto-disappearing dialogs */ H5P.JoubelMessageDialog = (function ($) { /** * Display a pop-up containing a message. * * @param {H5P.jQuery} $container The container which message dialog will be appended to * @param {string} message The message * @return {H5P.jQuery} */ function JoubelMessageDialog ($container, message) { var timeout; var removeDialog = function () { $warning.remove(); clearTimeout(timeout); $container.off('click.messageDialog'); }; // Create warning popup: var $warning = $('
', { 'class': 'joubel-message-dialog', text: message }).appendTo($container); // Remove after 3 seconds or if user clicks anywhere in $container: timeout = setTimeout(removeDialog, 3000); $container.on('click.messageDialog', removeDialog); return $warning; } return JoubelMessageDialog; })(H5P.jQuery); ; var H5P = H5P || {}; /** * Class responsible for creating a circular progress bar */ H5P.JoubelProgressCircle = (function ($) { /** * Constructor for the Progress Circle * * @param {Number} number The amount of progress to display * @param {string} progressColor Color for the progress meter * @param {string} backgroundColor Color behind the progress meter */ function ProgressCircle(number, progressColor, fillColor, backgroundColor) { progressColor = progressColor || '#1a73d9'; fillColor = fillColor || '#f0f0f0'; backgroundColor = backgroundColor || '#ffffff'; var progressColorRGB = this.hexToRgb(progressColor); //Verify number try { number = Number(number); if (number === '') { throw 'is empty'; } if (isNaN(number)) { throw 'is not a number'; } } catch (e) { number = 'err'; } //Draw circle if (number > 100) { number = 100; } // We can not use rgba, since they will stack on top of each other. // Instead we create the equivalent of the rgba color // and applies this to the activeborder and background color. var progressColorString = 'rgb(' + parseInt(progressColorRGB.r, 10) + ',' + parseInt(progressColorRGB.g, 10) + ',' + parseInt(progressColorRGB.b, 10) + ')'; // Circle wrapper var $wrapper = $('
', { 'class': "joubel-progress-circle-wrapper" }); //Active border indicates progress var $activeBorder = $('
', { 'class': "joubel-progress-circle-active-border" }).appendTo($wrapper); //Background circle var $backgroundCircle = $('
', { 'class': "joubel-progress-circle-circle" }).appendTo($activeBorder); //Progress text/number $('', { 'text': number + '%', 'class': "joubel-progress-circle-percentage" }).appendTo($backgroundCircle); var deg = number * 3.6; if (deg <= 180) { $activeBorder.css('background-image', 'linear-gradient(' + (90 + deg) + 'deg, transparent 50%, ' + fillColor + ' 50%),' + 'linear-gradient(90deg, ' + fillColor + ' 50%, transparent 50%)') .css('border', '2px solid' + backgroundColor) .css('background-color', progressColorString); } else { $activeBorder.css('background-image', 'linear-gradient(' + (deg - 90) + 'deg, transparent 50%, ' + progressColorString + ' 50%),' + 'linear-gradient(90deg, ' + fillColor + ' 50%, transparent 50%)') .css('border', '2px solid' + backgroundColor) .css('background-color', progressColorString); } this.$activeBorder = $activeBorder; this.$backgroundCircle = $backgroundCircle; this.$wrapper = $wrapper; this.initResizeFunctionality(); return $wrapper; } /** * Initializes resize functionality for the progress circle */ ProgressCircle.prototype.initResizeFunctionality = function () { var self = this; $(window).resize(function () { // Queue resize setTimeout(function () { self.resize(); }); }); // First resize setTimeout(function () { self.resize(); }, 0); }; /** * Resize function makes progress circle grow or shrink relative to parent container */ ProgressCircle.prototype.resize = function () { var $parent = this.$wrapper.parent(); if ($parent !== undefined && $parent) { // Measurements var fontSize = parseInt($parent.css('font-size'), 10); // Static sizes var fontSizeMultiplum = 3.75; var progressCircleWidthPx = parseInt((fontSize / 4.5), 10) % 2 === 0 ? parseInt((fontSize / 4.5), 10) + 4 : parseInt((fontSize / 4.5), 10) + 5; var progressCircleOffset = progressCircleWidthPx / 2; var width = fontSize * fontSizeMultiplum; var height = fontSize * fontSizeMultiplum; this.$activeBorder.css({ 'width': width, 'height': height }); this.$backgroundCircle.css({ 'width': width - progressCircleWidthPx, 'height': height - progressCircleWidthPx, 'top': progressCircleOffset, 'left': progressCircleOffset }); } }; /** * Hex to RGB conversion * @param hex * @returns {{r: Number, g: Number, b: Number}} */ ProgressCircle.prototype.hexToRgb = function (hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }; return ProgressCircle; }(H5P.jQuery)); ; var H5P = H5P || {}; H5P.SimpleRoundedButton = (function ($) { /** * Creates a new tip */ function SimpleRoundedButton(text) { var $simpleRoundedButton = $('
', { 'class': 'joubel-simple-rounded-button', 'title': text, 'role': 'button', 'tabindex': '0' }).keydown(function (e) { // 32 - space, 13 - enter if ([32, 13].indexOf(e.which) !== -1) { $(this).click(); e.preventDefault(); } }); $('', { 'class': 'joubel-simple-rounded-button-text', 'html': text }).appendTo($simpleRoundedButton); return $simpleRoundedButton; } return SimpleRoundedButton; }(H5P.jQuery)); ; var H5P = H5P || {}; /** * Class responsible for creating speech bubbles */ H5P.JoubelSpeechBubble = (function ($) { var $currentSpeechBubble; var $currentContainer; var $tail; var $innerTail; var removeSpeechBubbleTimeout; var currentMaxWidth; var DEFAULT_MAX_WIDTH = 400; var iDevice = navigator.userAgent.match(/iPod|iPhone|iPad/g) ? true : false; /** * Creates a new speech bubble * * @param {H5P.jQuery} $container The speaking object * @param {string} text The text to display * @param {number} maxWidth The maximum width of the bubble * @return {H5P.JoubelSpeechBubble} */ function JoubelSpeechBubble($container, text, maxWidth) { maxWidth = maxWidth || DEFAULT_MAX_WIDTH; currentMaxWidth = maxWidth; $currentContainer = $container; this.isCurrent = function ($tip) { return $tip.is($currentContainer); }; this.remove = function () { remove(); }; var fadeOutSpeechBubble = function ($speechBubble) { if (!$speechBubble) { return; } // Stop removing bubble clearTimeout(removeSpeechBubbleTimeout); $speechBubble.removeClass('show'); setTimeout(function () { if ($speechBubble) { $speechBubble.remove(); $speechBubble = undefined; } }, 500); }; if ($currentSpeechBubble !== undefined) { remove(); } var $h5pContainer = getH5PContainer($container); // Make sure we fade out old speech bubble fadeOutSpeechBubble($currentSpeechBubble); // Create bubble $tail = $('
'); $innerTail = $('
'); var $innerBubble = $( '
' + '
' + text + '
' + '
' ); $currentSpeechBubble = $( '
' ).append([$tail, $innerTail, $innerBubble]) .appendTo($h5pContainer); // Show speech bubble with transition setTimeout(function () { $currentSpeechBubble.addClass('show'); }, 0); position($currentSpeechBubble, $currentContainer, maxWidth, $tail, $innerTail); // Handle click to close H5P.$body.on('mousedown.speechBubble', handleOutsideClick); // Handle window resizing H5P.$window.on('resize', '', handleResize); // Handle clicks when inside IV which blocks bubbling. $container.parents('.h5p-dialog') .on('mousedown.speechBubble', handleOutsideClick); if (iDevice) { H5P.$body.css('cursor', 'pointer'); } return this; } // Remove speechbubble if it belongs to a dom element that is about to be hidden H5P.externalDispatcher.on('domHidden', function (event) { if ($currentSpeechBubble !== undefined && event.data.$dom.find($currentContainer).length !== 0) { remove(); } }); /** * Returns the closest h5p container for the given DOM element. * * @param {object} $container jquery element * @return {object} the h5p container (jquery element) */ function getH5PContainer($container) { var $h5pContainer = $container.closest('.h5p-frame'); // Check closest h5p frame first, then check for container in case there is no frame. if (!$h5pContainer.length) { $h5pContainer = $container.closest('.h5p-container'); } return $h5pContainer; } /** * Event handler that is called when the window is resized. */ function handleResize() { position($currentSpeechBubble, $currentContainer, currentMaxWidth, $tail, $innerTail); } /** * Repositions the speech bubble according to the position of the container. * * @param {object} $currentSpeechbubble the speech bubble that should be positioned * @param {object} $container the container to which the speech bubble should point * @param {number} maxWidth the maximum width of the speech bubble * @param {object} $tail the tail (the triangle that points to the referenced container) * @param {object} $innerTail the inner tail (the triangle that points to the referenced container) */ function position($currentSpeechBubble, $container, maxWidth, $tail, $innerTail) { var $h5pContainer = getH5PContainer($container); // Calculate offset between the button and the h5p frame var offset = getOffsetBetween($h5pContainer, $container); var direction = (offset.bottom > offset.top ? 'bottom' : 'top'); var tipWidth = offset.outerWidth * 0.9; // Var needs to be renamed to make sense var bubbleWidth = tipWidth > maxWidth ? maxWidth : tipWidth; var bubblePosition = getBubblePosition(bubbleWidth, offset); var tailPosition = getTailPosition(bubbleWidth, bubblePosition, offset, $container.width()); // Need to set font-size, since element is appended to body. // Using same font-size as parent. In that way it will grow accordingly // when resizing var fontSize = 16;//parseFloat($parent.css('font-size')); const isTop = direction === 'top'; $currentSpeechBubble.toggleClass('joubel-speech-bubble-top', isTop) .toggleClass('joubel-speech-bubble-bottom', !isTop); // Set width and position of speech bubble $currentSpeechBubble.css(bubbleCSS( direction, bubbleWidth, bubblePosition, fontSize )); var preparedTailCSS = tailCSS(direction, tailPosition); $tail.css(preparedTailCSS); $innerTail.css(preparedTailCSS); } /** * Static function for removing the speechbubble */ var remove = function () { H5P.$body.off('mousedown.speechBubble'); H5P.$window.off('resize', '', handleResize); $currentContainer.parents('.h5p-dialog').off('mousedown.speechBubble'); if (iDevice) { H5P.$body.css('cursor', ''); } if ($currentSpeechBubble !== undefined) { // Apply transition, then remove speech bubble $currentSpeechBubble.removeClass('show'); // Make sure we remove any old timeout before reassignment clearTimeout(removeSpeechBubbleTimeout); removeSpeechBubbleTimeout = setTimeout(function () { $currentSpeechBubble.remove(); $currentSpeechBubble = undefined; }, 500); } // Don't return false here. If the user e.g. clicks a button when the bubble is visible, // we want the bubble to disapear AND the button to receive the event }; /** * Remove the speech bubble and container reference */ function handleOutsideClick(event) { if (event.target === $currentContainer[0]) { return; // Button clicks are not outside clicks } remove(); // There is no current container when a container isn't clicked $currentContainer = undefined; } /** * Calculate position for speech bubble * * @param {number} bubbleWidth The width of the speech bubble * @param {object} offset * @return {object} Return position for the speech bubble */ function getBubblePosition(bubbleWidth, offset) { var bubblePosition = {}; var tailOffset = 9; var widthOffset = bubbleWidth / 2; // Calculate top position bubblePosition.top = offset.top + offset.innerHeight; // Calculate bottom position bubblePosition.bottom = offset.bottom + offset.innerHeight + tailOffset; // Calculate left position if (offset.left < widthOffset) { bubblePosition.left = 3; } else if ((offset.left + widthOffset) > offset.outerWidth) { bubblePosition.left = offset.outerWidth - bubbleWidth - 3; } else { bubblePosition.left = offset.left - widthOffset + (offset.innerWidth / 2); } return bubblePosition; } /** * Calculate position for speech bubble tail * * @param {number} bubbleWidth The width of the speech bubble * @param {object} bubblePosition Speech bubble position * @param {object} offset * @param {number} iconWidth The width of the tip icon * @return {object} Return position for the tail */ function getTailPosition(bubbleWidth, bubblePosition, offset, iconWidth) { var tailPosition = {}; // Magic numbers. Tuned by hand so that the tail fits visually within // the bounds of the speech bubble. var leftBoundary = 9; var rightBoundary = bubbleWidth - 20; tailPosition.left = offset.left - bubblePosition.left + (iconWidth / 2) - 6; if (tailPosition.left < leftBoundary) { tailPosition.left = leftBoundary; } if (tailPosition.left > rightBoundary) { tailPosition.left = rightBoundary; } tailPosition.top = -6; tailPosition.bottom = -6; return tailPosition; } /** * Return bubble CSS for the desired growth direction * * @param {string} direction The direction the speech bubble will grow * @param {number} width The width of the speech bubble * @param {object} position Speech bubble position * @param {number} fontSize The size of the bubbles font * @return {object} Return CSS */ function bubbleCSS(direction, width, position, fontSize) { if (direction === 'top') { return { width: width + 'px', bottom: position.bottom + 'px', left: position.left + 'px', fontSize: fontSize + 'px', top: '' }; } else { return { width: width + 'px', top: position.top + 'px', left: position.left + 'px', fontSize: fontSize + 'px', bottom: '' }; } } /** * Return tail CSS for the desired growth direction * * @param {string} direction The direction the speech bubble will grow * @param {object} position Tail position * @return {object} Return CSS */ function tailCSS(direction, position) { if (direction === 'top') { return { bottom: position.bottom + 'px', left: position.left + 'px', top: '' }; } else { return { top: position.top + 'px', left: position.left + 'px', bottom: '' }; } } /** * Calculates the offset between an element inside a container and the * container. Only works if all the edges of the inner element are inside the * outer element. * Width/height of the elements is included as a convenience. * * @param {H5P.jQuery} $outer * @param {H5P.jQuery} $inner * @return {object} Position offset */ function getOffsetBetween($outer, $inner) { var outer = $outer[0].getBoundingClientRect(); var inner = $inner[0].getBoundingClientRect(); return { top: inner.top - outer.top, right: outer.right - inner.right, bottom: outer.bottom - inner.bottom, left: inner.left - outer.left, innerWidth: inner.width, innerHeight: inner.height, outerWidth: outer.width, outerHeight: outer.height }; } return JoubelSpeechBubble; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelThrobber = (function ($) { /** * Creates a new tip */ function JoubelThrobber() { // h5p-throbber css is described in core var $throbber = $('
', { 'class': 'h5p-throbber' }); return $throbber; } return JoubelThrobber; }(H5P.jQuery)); ; H5P.JoubelTip = (function ($) { var $conv = $('
'); /** * Creates a new tip element. * * NOTE that this may look like a class but it doesn't behave like one. * It returns a jQuery object. * * @param {string} tipHtml The text to display in the popup * @param {Object} [behaviour] Options * @param {string} [behaviour.tipLabel] Set to use a custom label for the tip button (you want this for good A11Y) * @param {boolean} [behaviour.helpIcon] Set to 'true' to Add help-icon classname to Tip button (changes the icon) * @param {boolean} [behaviour.showSpeechBubble] Set to 'false' to disable functionality (you may this in the editor) * @param {boolean} [behaviour.tabcontrol] Set to 'true' if you plan on controlling the tabindex in the parent (tabindex="-1") * @return {H5P.jQuery|undefined} Tip button jQuery element or 'undefined' if invalid tip */ function JoubelTip(tipHtml, behaviour) { // Keep track of the popup that appears when you click the Tip button var speechBubble; // Parse tip html to determine text var tipText = $conv.html(tipHtml).text().trim(); if (tipText === '') { return; // The tip has no textual content, i.e. it's invalid. } // Set default behaviour behaviour = $.extend({ tipLabel: tipText, helpIcon: false, showSpeechBubble: true, tabcontrol: false }, behaviour); // Create Tip button var $tipButton = $('
', { class: 'joubel-tip-container' + (behaviour.showSpeechBubble ? '' : ' be-quiet'), 'aria-label': behaviour.tipLabel, 'aria-expanded': false, role: 'button', tabindex: (behaviour.tabcontrol ? -1 : 0), click: function (event) { // Toggle show/hide popup toggleSpeechBubble(); event.preventDefault(); }, keydown: function (event) { if (event.which === 32 || event.which === 13) { // Space & enter key // Toggle show/hide popup toggleSpeechBubble(); event.stopPropagation(); event.preventDefault(); } else { // Any other key // Toggle hide popup toggleSpeechBubble(false); } }, // Add markup to render icon html: '' + '' + '' + '' + '' // IMPORTANT: All of the markup elements must have 'pointer-events: none;' }); const $tipAnnouncer = $('
', { 'class': 'hidden-but-read', 'aria-live': 'polite', appendTo: $tipButton, }); /** * Tip button interaction handler. * Toggle show or hide the speech bubble popup when interacting with the * Tip button. * * @private * @param {boolean} [force] 'true' shows and 'false' hides. */ var toggleSpeechBubble = function (force) { if (speechBubble !== undefined && speechBubble.isCurrent($tipButton)) { // Hide current popup speechBubble.remove(); speechBubble = undefined; $tipButton.attr('aria-expanded', false); $tipAnnouncer.html(''); } else if (force !== false && behaviour.showSpeechBubble) { // Create and show new popup speechBubble = H5P.JoubelSpeechBubble($tipButton, tipHtml); $tipButton.attr('aria-expanded', true); $tipAnnouncer.html(tipHtml); } }; return $tipButton; } return JoubelTip; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelSlider = (function ($) { /** * Creates a new Slider * * @param {object} [params] Additional parameters */ function JoubelSlider(params) { H5P.EventDispatcher.call(this); this.$slider = $('
', $.extend({ 'class': 'h5p-joubel-ui-slider' }, params)); this.$slides = []; this.currentIndex = 0; this.numSlides = 0; } JoubelSlider.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelSlider.prototype.constructor = JoubelSlider; JoubelSlider.prototype.addSlide = function ($content) { $content.addClass('h5p-joubel-ui-slide').css({ 'left': (this.numSlides*100) + '%' }); this.$slider.append($content); this.$slides.push($content); this.numSlides++; if(this.numSlides === 1) { $content.addClass('current'); } }; JoubelSlider.prototype.attach = function ($container) { $container.append(this.$slider); }; JoubelSlider.prototype.move = function (index) { var self = this; if(index === 0) { self.trigger('first-slide'); } if(index+1 === self.numSlides) { self.trigger('last-slide'); } self.trigger('move'); var $previousSlide = self.$slides[this.currentIndex]; H5P.Transition.onTransitionEnd(this.$slider, function () { $previousSlide.removeClass('current'); self.trigger('moved'); }); this.$slides[index].addClass('current'); var translateX = 'translateX(' + (-index*100) + '%)'; this.$slider.css({ '-webkit-transform': translateX, '-moz-transform': translateX, '-ms-transform': translateX, 'transform': translateX }); this.currentIndex = index; }; JoubelSlider.prototype.remove = function () { this.$slider.remove(); }; JoubelSlider.prototype.next = function () { if(this.currentIndex+1 >= this.numSlides) { return; } this.move(this.currentIndex+1); }; JoubelSlider.prototype.previous = function () { this.move(this.currentIndex-1); }; JoubelSlider.prototype.first = function () { this.move(0); }; JoubelSlider.prototype.last = function () { this.move(this.numSlides-1); }; return JoubelSlider; })(H5P.jQuery); ; var H5P = H5P || {}; /** * @module */ H5P.JoubelScoreBar = (function ($) { /* Need to use an id for the star SVG since that is the only way to reference SVG filters */ var idCounter = 0; /** * Creates a score bar * @class H5P.JoubelScoreBar * @param {number} maxScore Maximum score * @param {string} [label] Makes it easier for readspeakers to identify the scorebar * @param {string} [helpText] Score explanation * @param {string} [scoreExplanationButtonLabel] Label for score explanation button */ function JoubelScoreBar(maxScore, label, helpText, scoreExplanationButtonLabel) { var self = this; self.maxScore = maxScore; self.score = 0; idCounter++; /** * @const {string} */ self.STAR_MARKUP = ''; /** * @function appendTo * @memberOf H5P.JoubelScoreBar# * @param {H5P.jQuery} $wrapper Dom container */ self.appendTo = function ($wrapper) { self.$scoreBar.appendTo($wrapper); }; /** * Create the text representation of the scorebar . * * @private * @return {string} */ var createLabel = function (score) { if (!label) { return ''; } return label.replace(':num', score).replace(':total', self.maxScore); }; /** * Creates the html for this widget * * @method createHtml * @private */ var createHtml = function () { // Container div self.$scoreBar = $('
', { 'class': 'h5p-joubelui-score-bar', }); var $visuals = $('
', { 'class': 'h5p-joubelui-score-bar-visuals', appendTo: self.$scoreBar }); // The progress bar wrapper self.$progressWrapper = $('
', { 'class': 'h5p-joubelui-score-bar-progress-wrapper', appendTo: $visuals }); self.$progress = $('
', { 'class': 'h5p-joubelui-score-bar-progress', 'html': createLabel(self.score), appendTo: self.$progressWrapper }); // The star $('
', { 'class': 'h5p-joubelui-score-bar-star', html: self.STAR_MARKUP }).appendTo($visuals); // The score container var $numerics = $('
', { 'class': 'h5p-joubelui-score-numeric', appendTo: self.$scoreBar, 'aria-hidden': true }); // The current score self.$scoreCounter = $('', { 'class': 'h5p-joubelui-score-number h5p-joubelui-score-number-counter', text: 0, appendTo: $numerics }); // The separator $('', { 'class': 'h5p-joubelui-score-number-separator', text: '/', appendTo: $numerics }); // Max score self.$maxScore = $('', { 'class': 'h5p-joubelui-score-number h5p-joubelui-score-max', text: self.maxScore, appendTo: $numerics }); if (helpText) { H5P.JoubelUI.createTip(helpText, { tipLabel: scoreExplanationButtonLabel ? scoreExplanationButtonLabel : helpText, helpIcon: true }).appendTo(self.$scoreBar); self.$scoreBar.addClass('h5p-score-bar-has-tip'); } }; /** * Set the current score * @method setScore * @memberOf H5P.JoubelScoreBar# * @param {number} score */ self.setScore = function (score) { // Do nothing if score hasn't changed if (score === self.score) { return; } self.score = score > self.maxScore ? self.maxScore : score; self.updateVisuals(); }; /** * Increment score * @method incrementScore * @memberOf H5P.JoubelScoreBar# * @param {number=} incrementBy Optional parameter, defaults to 1 */ self.incrementScore = function (incrementBy) { self.setScore(self.score + (incrementBy || 1)); }; /** * Set the max score * @method setMaxScore * @memberOf H5P.JoubelScoreBar# * @param {number} maxScore The max score */ self.setMaxScore = function (maxScore) { self.maxScore = maxScore; }; /** * Updates the progressbar visuals * @memberOf H5P.JoubelScoreBar# * @method updateVisuals */ self.updateVisuals = function () { self.$progress.html(createLabel(self.score)); self.$scoreCounter.text(self.score); self.$maxScore.text(self.maxScore); setTimeout(function () { // Start the progressbar animation self.$progress.css({ width: ((self.score / self.maxScore) * 100) + '%' }); H5P.Transition.onTransitionEnd(self.$progress, function () { // If fullscore fill the star and start the animation self.$scoreBar.toggleClass('h5p-joubelui-score-bar-full-score', self.score === self.maxScore); self.$scoreBar.toggleClass('h5p-joubelui-score-bar-animation-active', self.score === self.maxScore); // Only allow the star animation to run once self.$scoreBar.one("animationend", function () { self.$scoreBar.removeClass("h5p-joubelui-score-bar-animation-active"); }); }, 600); }, 300); }; /** * Removes all classes * @method reset */ self.reset = function () { self.$scoreBar.removeClass('h5p-joubelui-score-bar-full-score'); }; createHtml(); } return JoubelScoreBar; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelProgressbar = (function ($) { /** * Joubel progressbar class * @method JoubelProgressbar * @constructor * @param {number} steps Number of steps * @param {Object} [options] Additional options * @param {boolean} [options.disableAria] Disable readspeaker assistance * @param {string} [options.progressText] A progress text for describing * current progress out of total progress for readspeakers. * e.g. "Slide :num of :total" */ function JoubelProgressbar(steps, options) { H5P.EventDispatcher.call(this); var self = this; this.options = $.extend({ progressText: 'Slide :num of :total' }, options); this.currentStep = 0; this.steps = steps; this.$progressbar = $('
', { 'class': 'h5p-joubelui-progressbar' }); this.$background = $('
', { 'class': 'h5p-joubelui-progressbar-background' }).appendTo(this.$progressbar); } JoubelProgressbar.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelProgressbar.prototype.constructor = JoubelProgressbar; JoubelProgressbar.prototype.updateAria = function () { var self = this; if (this.options.disableAria) { return; } if (!this.$currentStatus) { this.$currentStatus = $('
', { 'class': 'h5p-joubelui-progressbar-slide-status-text', 'aria-live': 'assertive' }).appendTo(this.$progressbar); } var interpolatedProgressText = self.options.progressText .replace(':num', self.currentStep) .replace(':total', self.steps); this.$currentStatus.html(interpolatedProgressText); }; /** * Appends to a container * @method appendTo * @param {H5P.jquery} $container */ JoubelProgressbar.prototype.appendTo = function ($container) { this.$progressbar.appendTo($container); }; /** * Update progress * @method setProgress * @param {number} step */ JoubelProgressbar.prototype.setProgress = function (step) { // Check for valid value: if (step > this.steps || step < 0) { return; } this.currentStep = step; this.$background.css({ width: ((this.currentStep/this.steps)*100) + '%' }); this.updateAria(); }; /** * Increment progress with 1 * @method next */ JoubelProgressbar.prototype.next = function () { this.setProgress(this.currentStep+1); }; /** * Reset progressbar * @method reset */ JoubelProgressbar.prototype.reset = function () { this.setProgress(0); }; /** * Check if last step is reached * @method isLastStep * @return {Boolean} */ JoubelProgressbar.prototype.isLastStep = function () { return this.steps === this.currentStep; }; return JoubelProgressbar; })(H5P.jQuery); ; var H5P = H5P || {}; /** * H5P Joubel UI library. * * This is a utility library, which does not implement attach. I.e, it has to bee actively used by * other libraries * @module */ H5P.JoubelUI = (function ($) { /** * The internal object to return * @class H5P.JoubelUI * @static */ function JoubelUI() {} /* Public static functions */ /** * Create a tip icon * @method H5P.JoubelUI.createTip * @param {string} text The textual tip * @param {Object} params Parameters * @return {H5P.JoubelTip} */ JoubelUI.createTip = function (text, params) { return new H5P.JoubelTip(text, params); }; /** * Create message dialog * @method H5P.JoubelUI.createMessageDialog * @param {H5P.jQuery} $container The dom container * @param {string} message The message * @return {H5P.JoubelMessageDialog} */ JoubelUI.createMessageDialog = function ($container, message) { return new H5P.JoubelMessageDialog($container, message); }; /** * Create help text dialog * @method H5P.JoubelUI.createHelpTextDialog * @param {string} header The textual header * @param {string} message The textual message * @param {string} closeButtonTitle The title for the close button * @return {H5P.JoubelHelpTextDialog} */ JoubelUI.createHelpTextDialog = function (header, message, closeButtonTitle) { return new H5P.JoubelHelpTextDialog(header, message, closeButtonTitle); }; /** * Create progress circle * @method H5P.JoubelUI.createProgressCircle * @param {number} number The progress (0 to 100) * @param {string} progressColor The progress color in hex value * @param {string} fillColor The fill color in hex value * @param {string} backgroundColor The background color in hex value * @return {H5P.JoubelProgressCircle} */ JoubelUI.createProgressCircle = function (number, progressColor, fillColor, backgroundColor) { return new H5P.JoubelProgressCircle(number, progressColor, fillColor, backgroundColor); }; /** * Create throbber for loading * @method H5P.JoubelUI.createThrobber * @return {H5P.JoubelThrobber} */ JoubelUI.createThrobber = function () { return new H5P.JoubelThrobber(); }; /** * Create simple rounded button * @method H5P.JoubelUI.createSimpleRoundedButton * @param {string} text The button label * @return {H5P.SimpleRoundedButton} */ JoubelUI.createSimpleRoundedButton = function (text) { return new H5P.SimpleRoundedButton(text); }; /** * Create Slider * @method H5P.JoubelUI.createSlider * @param {Object} [params] Parameters * @return {H5P.JoubelSlider} */ JoubelUI.createSlider = function (params) { return new H5P.JoubelSlider(params); }; /** * Create Score Bar * @method H5P.JoubelUI.createScoreBar * @param {number=} maxScore The maximum score * @param {string} [label] Makes it easier for readspeakers to identify the scorebar * @return {H5P.JoubelScoreBar} */ JoubelUI.createScoreBar = function (maxScore, label, helpText, scoreExplanationButtonLabel) { return new H5P.JoubelScoreBar(maxScore, label, helpText, scoreExplanationButtonLabel); }; /** * Create Progressbar * @method H5P.JoubelUI.createProgressbar * @param {number=} numSteps The total numer of steps * @param {Object} [options] Additional options * @param {boolean} [options.disableAria] Disable readspeaker assistance * @param {string} [options.progressText] A progress text for describing * current progress out of total progress for readspeakers. * e.g. "Slide :num of :total" * @return {H5P.JoubelProgressbar} */ JoubelUI.createProgressbar = function (numSteps, options) { return new H5P.JoubelProgressbar(numSteps, options); }; /** * Create standard Joubel button * * @method H5P.JoubelUI.createButton * @param {object} params * May hold any properties allowed by jQuery. If href is set, an A tag * is used, if not a button tag is used. * @return {H5P.jQuery} The jquery element created */ JoubelUI.createButton = function(params) { var type = 'button'; if (params.href) { type = 'a'; } else { params.type = 'button'; } if (params.class) { params.class += ' h5p-joubelui-button'; } else { params.class = 'h5p-joubelui-button'; } return $('<' + type + '/>', params); }; /** * Fix for iframe scoll bug in IOS. When focusing an element that doesn't have * focus support by default the iframe will scroll the parent frame so that * the focused element is out of view. This varies dependening on the elements * of the parent frame. */ if (H5P.isFramed && !H5P.hasiOSiframeScrollFix && /iPad|iPhone|iPod/.test(navigator.userAgent)) { H5P.hasiOSiframeScrollFix = true; // Keep track of original focus function var focus = HTMLElement.prototype.focus; // Override the original focus HTMLElement.prototype.focus = function () { // Only focus the element if it supports it natively if ( (this instanceof HTMLAnchorElement || this instanceof HTMLInputElement || this instanceof HTMLSelectElement || this instanceof HTMLTextAreaElement || this instanceof HTMLButtonElement || this instanceof HTMLIFrameElement || this instanceof HTMLAreaElement) && // HTMLAreaElement isn't supported by Safari yet. !this.getAttribute('role')) { // Focus breaks if a different role has been set // In theory this.isContentEditable should be able to recieve focus, // but it didn't work when tested. // Trigger the original focus with the proper context focus.call(this); } }; } return JoubelUI; })(H5P.jQuery); ; var oldJQuery = jQuery; var jQuery = H5P.jQuery; /*! jQuery UI - v1.13.0 - 2021-10-07 * http://jqueryui.com * Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ ( function( factory ) { "use strict"; if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "jquery" ], factory ); } else { // Browser globals factory( jQuery ); } } )( function( $ ) { "use strict"; $.ui = $.ui || {}; var version = $.ui.version = "1.13.0"; /*! * jQuery UI Widget 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Widget //>>group: Core //>>description: Provides a factory for creating stateful widgets with a common API. //>>docs: http://api.jqueryui.com/jQuery.widget/ //>>demos: http://jqueryui.com/widget/ var widgetUuid = 0; var widgetHasOwnProperty = Array.prototype.hasOwnProperty; var widgetSlice = Array.prototype.slice; $.cleanData = ( function( orig ) { return function( elems ) { var events, elem, i; for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { // Only trigger remove when necessary to save time events = $._data( elem, "events" ); if ( events && events.remove ) { $( elem ).triggerHandler( "remove" ); } } orig( elems ); }; } )( $.cleanData ); $.widget = function( name, base, prototype ) { var existingConstructor, constructor, basePrototype; // ProxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) var proxiedPrototype = {}; var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; var fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } if ( Array.isArray( prototype ) ) { prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); } // Create selector for plugin $.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // Allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // Allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // Extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // Copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // Track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] } ); basePrototype = new base(); // We need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( typeof value !== "function" ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = ( function() { function _super() { return base.prototype[ prop ].apply( this, arguments ); } function _superApply( args ) { return base.prototype[ prop ].apply( this, args ); } return function() { var __super = this._super; var __superApply = this._superApply; var returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; } )(); } ); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName } ); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // Redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); } ); // Remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); return constructor; }; $.widget.extend = function( target ) { var input = widgetSlice.call( arguments, 1 ); var inputIndex = 0; var inputLength = input.length; var key; var value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string"; var args = widgetSlice.call( arguments, 1 ); var returnValue = this; if ( isMethodCall ) { // If this is an empty collection, we need to have the instance method // return undefined instead of the jQuery instance if ( !this.length && options === "instance" ) { returnValue = undefined; } else { this.each( function() { var methodValue; var instance = $.data( this, fullName ); if ( options === "instance" ) { returnValue = instance; return false; } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( typeof instance[ options ] !== "function" || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } } ); } } else { // Allow multiple hashes to be passed on init if ( args.length ) { options = $.widget.extend.apply( null, [ options ].concat( args ) ); } this.each( function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} ); if ( instance._init ) { instance._init(); } } else { $.data( this, fullName, new object( options, this ) ); } } ); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "
", options: { classes: {}, disabled: false, // Callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = widgetUuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.bindings = $(); this.hoverable = $(); this.focusable = $(); this.classesElementLookup = {}; if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } } ); this.document = $( element.style ? // Element within the document element.ownerDocument : // Element is window or document element.document || element ); this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); } this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this._create(); if ( this.options.disabled ) { this._setOptionDisabled( this.options.disabled ); } this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: function() { return {}; }, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { var that = this; this._destroy(); $.each( this.classesElementLookup, function( key, value ) { that._removeClass( value, key ); } ); // We can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .off( this.eventNamespace ) .removeData( this.widgetFullName ); this.widget() .off( this.eventNamespace ) .removeAttr( "aria-disabled" ); // Clean up events and states this.bindings.off( this.eventNamespace ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key; var parts; var curOption; var i; if ( arguments.length === 0 ) { // Don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { if ( key === "classes" ) { this._setOptionClasses( value ); } this.options[ key ] = value; if ( key === "disabled" ) { this._setOptionDisabled( value ); } return this; }, _setOptionClasses: function( value ) { var classKey, elements, currentElements; for ( classKey in value ) { currentElements = this.classesElementLookup[ classKey ]; if ( value[ classKey ] === this.options.classes[ classKey ] || !currentElements || !currentElements.length ) { continue; } // We are doing this to create a new jQuery object because the _removeClass() call // on the next line is going to destroy the reference to the current elements being // tracked. We need to save a copy of this collection so that we can add the new classes // below. elements = $( currentElements.get() ); this._removeClass( currentElements, classKey ); // We don't use _addClass() here, because that uses this.options.classes // for generating the string of classes. We want to use the value passed in from // _setOption(), this is the new value of the classes option which was passed to // _setOption(). We pass this value directly to _classes(). elements.addClass( this._classes( { element: elements, keys: classKey, classes: value, add: true } ) ); } }, _setOptionDisabled: function( value ) { this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); // If the widget is becoming disabled, then nothing is interactive if ( value ) { this._removeClass( this.hoverable, null, "ui-state-hover" ); this._removeClass( this.focusable, null, "ui-state-focus" ); } }, enable: function() { return this._setOptions( { disabled: false } ); }, disable: function() { return this._setOptions( { disabled: true } ); }, _classes: function( options ) { var full = []; var that = this; options = $.extend( { element: this.element, classes: this.options.classes || {} }, options ); function bindRemoveEvent() { options.element.each( function( _, element ) { var isTracked = $.map( that.classesElementLookup, function( elements ) { return elements; } ) .some( function( elements ) { return elements.is( element ); } ); if ( !isTracked ) { that._on( $( element ), { remove: "_untrackClassesElement" } ); } } ); } function processClassString( classes, checkOption ) { var current, i; for ( i = 0; i < classes.length; i++ ) { current = that.classesElementLookup[ classes[ i ] ] || $(); if ( options.add ) { bindRemoveEvent(); current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) ); } else { current = $( current.not( options.element ).get() ); } that.classesElementLookup[ classes[ i ] ] = current; full.push( classes[ i ] ); if ( checkOption && options.classes[ classes[ i ] ] ) { full.push( options.classes[ classes[ i ] ] ); } } } if ( options.keys ) { processClassString( options.keys.match( /\S+/g ) || [], true ); } if ( options.extra ) { processClassString( options.extra.match( /\S+/g ) || [] ); } return full.join( " " ); }, _untrackClassesElement: function( event ) { var that = this; $.each( that.classesElementLookup, function( key, value ) { if ( $.inArray( event.target, value ) !== -1 ) { that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); } } ); this._off( $( event.target ) ); }, _removeClass: function( element, keys, extra ) { return this._toggleClass( element, keys, extra, false ); }, _addClass: function( element, keys, extra ) { return this._toggleClass( element, keys, extra, true ); }, _toggleClass: function( element, keys, extra, add ) { add = ( typeof add === "boolean" ) ? add : extra; var shift = ( typeof element === "string" || element === null ), options = { extra: shift ? keys : extra, keys: shift ? element : keys, element: shift ? this.element : element, add: add }; options.element.toggleClass( this._classes( options ), add ); return this; }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement; var instance = this; // No suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // No element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // Allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // Copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^([\w:-]*)\s*(.*)$/ ); var eventName = match[ 1 ] + instance.eventNamespace; var selector = match[ 2 ]; if ( selector ) { delegateElement.on( eventName, selector, handlerProxy ); } else { element.on( eventName, handlerProxy ); } } ); }, _off: function( element, eventName ) { eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.off( eventName ); // Clear the stack to avoid memory leaks (#10056) this.bindings = $( this.bindings.not( element ).get() ); this.focusable = $( this.focusable.not( element ).get() ); this.hoverable = $( this.hoverable.not( element ).get() ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); }, mouseleave: function( event ) { this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); } } ); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); }, focusout: function( event ) { this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); } } ); }, _trigger: function( type, event, data ) { var prop, orig; var callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // The original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // Copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( typeof callback === "function" && callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions; var effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } else if ( options === true ) { options = {}; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue( function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); } ); } }; } ); var widget = $.widget; /*! * jQuery UI Position 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/position/ */ //>>label: Position //>>group: Core //>>description: Positions elements relative to other elements. //>>docs: http://api.jqueryui.com/position/ //>>demos: http://jqueryui.com/position/ ( function() { var cachedScrollbarWidth, max = Math.max, abs = Math.abs, rhorizontal = /left|center|right/, rvertical = /top|center|bottom/, roffset = /[\+\-]\d+(\.[\d]+)?%?/, rposition = /^\w+/, rpercent = /%$/, _position = $.fn.position; function getOffsets( offsets, width, height ) { return [ parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) ]; } function parseCss( element, property ) { return parseInt( $.css( element, property ), 10 ) || 0; } function isWindow( obj ) { return obj != null && obj === obj.window; } function getDimensions( elem ) { var raw = elem[ 0 ]; if ( raw.nodeType === 9 ) { return { width: elem.width(), height: elem.height(), offset: { top: 0, left: 0 } }; } if ( isWindow( raw ) ) { return { width: elem.width(), height: elem.height(), offset: { top: elem.scrollTop(), left: elem.scrollLeft() } }; } if ( raw.preventDefault ) { return { width: 0, height: 0, offset: { top: raw.pageY, left: raw.pageX } }; } return { width: elem.outerWidth(), height: elem.outerHeight(), offset: elem.offset() }; } $.position = { scrollbarWidth: function() { if ( cachedScrollbarWidth !== undefined ) { return cachedScrollbarWidth; } var w1, w2, div = $( "
" + "
" ), innerDiv = div.children()[ 0 ]; $( "body" ).append( div ); w1 = innerDiv.offsetWidth; div.css( "overflow", "scroll" ); w2 = innerDiv.offsetWidth; if ( w1 === w2 ) { w2 = div[ 0 ].clientWidth; } div.remove(); return ( cachedScrollbarWidth = w1 - w2 ); }, getScrollInfo: function( within ) { var overflowX = within.isWindow || within.isDocument ? "" : within.element.css( "overflow-x" ), overflowY = within.isWindow || within.isDocument ? "" : within.element.css( "overflow-y" ), hasOverflowX = overflowX === "scroll" || ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), hasOverflowY = overflowY === "scroll" || ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); return { width: hasOverflowY ? $.position.scrollbarWidth() : 0, height: hasOverflowX ? $.position.scrollbarWidth() : 0 }; }, getWithinInfo: function( element ) { var withinElement = $( element || window ), isElemWindow = isWindow( withinElement[ 0 ] ), isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, hasOffset = !isElemWindow && !isDocument; return { element: withinElement, isWindow: isElemWindow, isDocument: isDocument, offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, scrollLeft: withinElement.scrollLeft(), scrollTop: withinElement.scrollTop(), width: withinElement.outerWidth(), height: withinElement.outerHeight() }; } }; $.fn.position = function( options ) { if ( !options || !options.of ) { return _position.apply( this, arguments ); } // Make a copy, we don't want to modify arguments options = $.extend( {}, options ); var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, // Make sure string options are treated as CSS selectors target = typeof options.of === "string" ? $( document ).find( options.of ) : $( options.of ), within = $.position.getWithinInfo( options.within ), scrollInfo = $.position.getScrollInfo( within ), collision = ( options.collision || "flip" ).split( " " ), offsets = {}; dimensions = getDimensions( target ); if ( target[ 0 ].preventDefault ) { // Force left top to allow flipping options.at = "left top"; } targetWidth = dimensions.width; targetHeight = dimensions.height; targetOffset = dimensions.offset; // Clone to reuse original targetOffset later basePosition = $.extend( {}, targetOffset ); // Force my and at to have valid horizontal and vertical positions // if a value is missing or invalid, it will be converted to center $.each( [ "my", "at" ], function() { var pos = ( options[ this ] || "" ).split( " " ), horizontalOffset, verticalOffset; if ( pos.length === 1 ) { pos = rhorizontal.test( pos[ 0 ] ) ? pos.concat( [ "center" ] ) : rvertical.test( pos[ 0 ] ) ? [ "center" ].concat( pos ) : [ "center", "center" ]; } pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; // Calculate offsets horizontalOffset = roffset.exec( pos[ 0 ] ); verticalOffset = roffset.exec( pos[ 1 ] ); offsets[ this ] = [ horizontalOffset ? horizontalOffset[ 0 ] : 0, verticalOffset ? verticalOffset[ 0 ] : 0 ]; // Reduce to just the positions without the offsets options[ this ] = [ rposition.exec( pos[ 0 ] )[ 0 ], rposition.exec( pos[ 1 ] )[ 0 ] ]; } ); // Normalize collision option if ( collision.length === 1 ) { collision[ 1 ] = collision[ 0 ]; } if ( options.at[ 0 ] === "right" ) { basePosition.left += targetWidth; } else if ( options.at[ 0 ] === "center" ) { basePosition.left += targetWidth / 2; } if ( options.at[ 1 ] === "bottom" ) { basePosition.top += targetHeight; } else if ( options.at[ 1 ] === "center" ) { basePosition.top += targetHeight / 2; } atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); basePosition.left += atOffset[ 0 ]; basePosition.top += atOffset[ 1 ]; return this.each( function() { var collisionPosition, using, elem = $( this ), elemWidth = elem.outerWidth(), elemHeight = elem.outerHeight(), marginLeft = parseCss( this, "marginLeft" ), marginTop = parseCss( this, "marginTop" ), collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, position = $.extend( {}, basePosition ), myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); if ( options.my[ 0 ] === "right" ) { position.left -= elemWidth; } else if ( options.my[ 0 ] === "center" ) { position.left -= elemWidth / 2; } if ( options.my[ 1 ] === "bottom" ) { position.top -= elemHeight; } else if ( options.my[ 1 ] === "center" ) { position.top -= elemHeight / 2; } position.left += myOffset[ 0 ]; position.top += myOffset[ 1 ]; collisionPosition = { marginLeft: marginLeft, marginTop: marginTop }; $.each( [ "left", "top" ], function( i, dir ) { if ( $.ui.position[ collision[ i ] ] ) { $.ui.position[ collision[ i ] ][ dir ]( position, { targetWidth: targetWidth, targetHeight: targetHeight, elemWidth: elemWidth, elemHeight: elemHeight, collisionPosition: collisionPosition, collisionWidth: collisionWidth, collisionHeight: collisionHeight, offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], my: options.my, at: options.at, within: within, elem: elem } ); } } ); if ( options.using ) { // Adds feedback as second argument to using callback, if present using = function( props ) { var left = targetOffset.left - position.left, right = left + targetWidth - elemWidth, top = targetOffset.top - position.top, bottom = top + targetHeight - elemHeight, feedback = { target: { element: target, left: targetOffset.left, top: targetOffset.top, width: targetWidth, height: targetHeight }, element: { element: elem, left: position.left, top: position.top, width: elemWidth, height: elemHeight }, horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" }; if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { feedback.horizontal = "center"; } if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { feedback.vertical = "middle"; } if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { feedback.important = "horizontal"; } else { feedback.important = "vertical"; } options.using.call( this, props, feedback ); }; } elem.offset( $.extend( position, { using: using } ) ); } ); }; $.ui.position = { fit: { left: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, outerWidth = within.width, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = withinOffset - collisionPosLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, newOverRight; // Element is wider than within if ( data.collisionWidth > outerWidth ) { // Element is initially over the left side of within if ( overLeft > 0 && overRight <= 0 ) { newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; position.left += overLeft - newOverRight; // Element is initially over right side of within } else if ( overRight > 0 && overLeft <= 0 ) { position.left = withinOffset; // Element is initially over both left and right sides of within } else { if ( overLeft > overRight ) { position.left = withinOffset + outerWidth - data.collisionWidth; } else { position.left = withinOffset; } } // Too far left -> align with left edge } else if ( overLeft > 0 ) { position.left += overLeft; // Too far right -> align with right edge } else if ( overRight > 0 ) { position.left -= overRight; // Adjust based on position and margin } else { position.left = max( position.left - collisionPosLeft, position.left ); } }, top: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollTop : within.offset.top, outerHeight = data.within.height, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = withinOffset - collisionPosTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, newOverBottom; // Element is taller than within if ( data.collisionHeight > outerHeight ) { // Element is initially over the top of within if ( overTop > 0 && overBottom <= 0 ) { newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; position.top += overTop - newOverBottom; // Element is initially over bottom of within } else if ( overBottom > 0 && overTop <= 0 ) { position.top = withinOffset; // Element is initially over both top and bottom of within } else { if ( overTop > overBottom ) { position.top = withinOffset + outerHeight - data.collisionHeight; } else { position.top = withinOffset; } } // Too far up -> align with top } else if ( overTop > 0 ) { position.top += overTop; // Too far down -> align with bottom edge } else if ( overBottom > 0 ) { position.top -= overBottom; // Adjust based on position and margin } else { position.top = max( position.top - collisionPosTop, position.top ); } } }, flip: { left: function( position, data ) { var within = data.within, withinOffset = within.offset.left + within.scrollLeft, outerWidth = within.width, offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = collisionPosLeft - offsetLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, myOffset = data.my[ 0 ] === "left" ? -data.elemWidth : data.my[ 0 ] === "right" ? data.elemWidth : 0, atOffset = data.at[ 0 ] === "left" ? data.targetWidth : data.at[ 0 ] === "right" ? -data.targetWidth : 0, offset = -2 * data.offset[ 0 ], newOverRight, newOverLeft; if ( overLeft < 0 ) { newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { position.left += myOffset + atOffset + offset; } } else if ( overRight > 0 ) { newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { position.left += myOffset + atOffset + offset; } } }, top: function( position, data ) { var within = data.within, withinOffset = within.offset.top + within.scrollTop, outerHeight = within.height, offsetTop = within.isWindow ? within.scrollTop : within.offset.top, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = collisionPosTop - offsetTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, top = data.my[ 1 ] === "top", myOffset = top ? -data.elemHeight : data.my[ 1 ] === "bottom" ? data.elemHeight : 0, atOffset = data.at[ 1 ] === "top" ? data.targetHeight : data.at[ 1 ] === "bottom" ? -data.targetHeight : 0, offset = -2 * data.offset[ 1 ], newOverTop, newOverBottom; if ( overTop < 0 ) { newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { position.top += myOffset + atOffset + offset; } } else if ( overBottom > 0 ) { newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { position.top += myOffset + atOffset + offset; } } } }, flipfit: { left: function() { $.ui.position.flip.left.apply( this, arguments ); $.ui.position.fit.left.apply( this, arguments ); }, top: function() { $.ui.position.flip.top.apply( this, arguments ); $.ui.position.fit.top.apply( this, arguments ); } } }; } )(); var position = $.ui.position; /*! * jQuery UI :data 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: :data Selector //>>group: Core //>>description: Selects elements which have data stored under the specified key. //>>docs: http://api.jqueryui.com/data-selector/ var data = $.extend( $.expr.pseudos, { data: $.expr.createPseudo ? $.expr.createPseudo( function( dataName ) { return function( elem ) { return !!$.data( elem, dataName ); }; } ) : // Support: jQuery <1.8 function( elem, i, match ) { return !!$.data( elem, match[ 3 ] ); } } ); /*! * jQuery UI Disable Selection 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: disableSelection //>>group: Core //>>description: Disable selection of text content within the set of matched elements. //>>docs: http://api.jqueryui.com/disableSelection/ // This file is deprecated var disableSelection = $.fn.extend( { disableSelection: ( function() { var eventType = "onselectstart" in document.createElement( "div" ) ? "selectstart" : "mousedown"; return function() { return this.on( eventType + ".ui-disableSelection", function( event ) { event.preventDefault(); } ); }; } )(), enableSelection: function() { return this.off( ".ui-disableSelection" ); } } ); // Create a local jQuery because jQuery Color relies on it and the // global may not exist with AMD and a custom build (#10199). // This module is a noop if used as a regular AMD module. // eslint-disable-next-line no-unused-vars var jQuery = $; /*! * jQuery Color Animations v2.2.0 * https://github.com/jquery/jquery-color * * Copyright OpenJS Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * Date: Sun May 10 09:02:36 2020 +0200 */ var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " + "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", class2type = {}, toString = class2type.toString, // plusequals test for += 100 -= 100 rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, // a set of RE's that can match strings and generate color tuples. stringParsers = [ { re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ execResult[ 1 ], execResult[ 2 ], execResult[ 3 ], execResult[ 4 ] ]; } }, { re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ execResult[ 1 ] * 2.55, execResult[ 2 ] * 2.55, execResult[ 3 ] * 2.55, execResult[ 4 ] ]; } }, { // this regex ignores A-F because it's compared against an already lowercased string re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})?/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ], 16 ), parseInt( execResult[ 2 ], 16 ), parseInt( execResult[ 3 ], 16 ), execResult[ 4 ] ? ( parseInt( execResult[ 4 ], 16 ) / 255 ).toFixed( 2 ) : 1 ]; } }, { // this regex ignores A-F because it's compared against an already lowercased string re: /#([a-f0-9])([a-f0-9])([a-f0-9])([a-f0-9])?/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ), execResult[ 4 ] ? ( parseInt( execResult[ 4 ] + execResult[ 4 ], 16 ) / 255 ) .toFixed( 2 ) : 1 ]; } }, { re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, space: "hsla", parse: function( execResult ) { return [ execResult[ 1 ], execResult[ 2 ] / 100, execResult[ 3 ] / 100, execResult[ 4 ] ]; } } ], // jQuery.Color( ) color = jQuery.Color = function( color, green, blue, alpha ) { return new jQuery.Color.fn.parse( color, green, blue, alpha ); }, spaces = { rgba: { props: { red: { idx: 0, type: "byte" }, green: { idx: 1, type: "byte" }, blue: { idx: 2, type: "byte" } } }, hsla: { props: { hue: { idx: 0, type: "degrees" }, saturation: { idx: 1, type: "percent" }, lightness: { idx: 2, type: "percent" } } } }, propTypes = { "byte": { floor: true, max: 255 }, "percent": { max: 1 }, "degrees": { mod: 360, floor: true } }, support = color.support = {}, // element for support tests supportElem = jQuery( "

" )[ 0 ], // colors = jQuery.Color.names colors, // local aliases of functions called often each = jQuery.each; // determine rgba support immediately supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; // define cache name and alpha properties // for rgba and hsla spaces each( spaces, function( spaceName, space ) { space.cache = "_" + spaceName; space.props.alpha = { idx: 3, type: "percent", def: 1 }; } ); // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), function( _i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); function getType( obj ) { if ( obj == null ) { return obj + ""; } return typeof obj === "object" ? class2type[ toString.call( obj ) ] || "object" : typeof obj; } function clamp( value, prop, allowEmpty ) { var type = propTypes[ prop.type ] || {}; if ( value == null ) { return ( allowEmpty || !prop.def ) ? null : prop.def; } // ~~ is an short way of doing floor for positive numbers value = type.floor ? ~~value : parseFloat( value ); // IE will pass in empty strings as value for alpha, // which will hit this case if ( isNaN( value ) ) { return prop.def; } if ( type.mod ) { // we add mod before modding to make sure that negatives values // get converted properly: -10 -> 350 return ( value + type.mod ) % type.mod; } // for now all property types without mod have min and max return Math.min( type.max, Math.max( 0, value ) ); } function stringParse( string ) { var inst = color(), rgba = inst._rgba = []; string = string.toLowerCase(); each( stringParsers, function( _i, parser ) { var parsed, match = parser.re.exec( string ), values = match && parser.parse( match ), spaceName = parser.space || "rgba"; if ( values ) { parsed = inst[ spaceName ]( values ); // if this was an rgba parse the assignment might happen twice // oh well.... inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; rgba = inst._rgba = parsed._rgba; // exit each( stringParsers ) here because we matched return false; } } ); // Found a stringParser that handled it if ( rgba.length ) { // if this came from a parsed string, force "transparent" when alpha is 0 // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) if ( rgba.join() === "0,0,0,0" ) { jQuery.extend( rgba, colors.transparent ); } return inst; } // named colors return colors[ string ]; } color.fn = jQuery.extend( color.prototype, { parse: function( red, green, blue, alpha ) { if ( red === undefined ) { this._rgba = [ null, null, null, null ]; return this; } if ( red.jquery || red.nodeType ) { red = jQuery( red ).css( green ); green = undefined; } var inst = this, type = getType( red ), rgba = this._rgba = []; // more than 1 argument specified - assume ( red, green, blue, alpha ) if ( green !== undefined ) { red = [ red, green, blue, alpha ]; type = "array"; } if ( type === "string" ) { return this.parse( stringParse( red ) || colors._default ); } if ( type === "array" ) { each( spaces.rgba.props, function( _key, prop ) { rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); } ); return this; } if ( type === "object" ) { if ( red instanceof color ) { each( spaces, function( _spaceName, space ) { if ( red[ space.cache ] ) { inst[ space.cache ] = red[ space.cache ].slice(); } } ); } else { each( spaces, function( _spaceName, space ) { var cache = space.cache; each( space.props, function( key, prop ) { // if the cache doesn't exist, and we know how to convert if ( !inst[ cache ] && space.to ) { // if the value was null, we don't need to copy it // if the key was alpha, we don't need to copy it either if ( key === "alpha" || red[ key ] == null ) { return; } inst[ cache ] = space.to( inst._rgba ); } // this is the only case where we allow nulls for ALL properties. // call clamp with alwaysAllowEmpty inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); } ); // everything defined but alpha? if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { // use the default of 1 if ( inst[ cache ][ 3 ] == null ) { inst[ cache ][ 3 ] = 1; } if ( space.from ) { inst._rgba = space.from( inst[ cache ] ); } } } ); } return this; } }, is: function( compare ) { var is = color( compare ), same = true, inst = this; each( spaces, function( _, space ) { var localCache, isCache = is[ space.cache ]; if ( isCache ) { localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; each( space.props, function( _, prop ) { if ( isCache[ prop.idx ] != null ) { same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); return same; } } ); } return same; } ); return same; }, _space: function() { var used = [], inst = this; each( spaces, function( spaceName, space ) { if ( inst[ space.cache ] ) { used.push( spaceName ); } } ); return used.pop(); }, transition: function( other, distance ) { var end = color( other ), spaceName = end._space(), space = spaces[ spaceName ], startColor = this.alpha() === 0 ? color( "transparent" ) : this, start = startColor[ space.cache ] || space.to( startColor._rgba ), result = start.slice(); end = end[ space.cache ]; each( space.props, function( _key, prop ) { var index = prop.idx, startValue = start[ index ], endValue = end[ index ], type = propTypes[ prop.type ] || {}; // if null, don't override start value if ( endValue === null ) { return; } // if null - use end if ( startValue === null ) { result[ index ] = endValue; } else { if ( type.mod ) { if ( endValue - startValue > type.mod / 2 ) { startValue += type.mod; } else if ( startValue - endValue > type.mod / 2 ) { startValue -= type.mod; } } result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); } } ); return this[ spaceName ]( result ); }, blend: function( opaque ) { // if we are already opaque - return ourself if ( this._rgba[ 3 ] === 1 ) { return this; } var rgb = this._rgba.slice(), a = rgb.pop(), blend = color( opaque )._rgba; return color( jQuery.map( rgb, function( v, i ) { return ( 1 - a ) * blend[ i ] + a * v; } ) ); }, toRgbaString: function() { var prefix = "rgba(", rgba = jQuery.map( this._rgba, function( v, i ) { if ( v != null ) { return v; } return i > 2 ? 1 : 0; } ); if ( rgba[ 3 ] === 1 ) { rgba.pop(); prefix = "rgb("; } return prefix + rgba.join() + ")"; }, toHslaString: function() { var prefix = "hsla(", hsla = jQuery.map( this.hsla(), function( v, i ) { if ( v == null ) { v = i > 2 ? 1 : 0; } // catch 1 and 2 if ( i && i < 3 ) { v = Math.round( v * 100 ) + "%"; } return v; } ); if ( hsla[ 3 ] === 1 ) { hsla.pop(); prefix = "hsl("; } return prefix + hsla.join() + ")"; }, toHexString: function( includeAlpha ) { var rgba = this._rgba.slice(), alpha = rgba.pop(); if ( includeAlpha ) { rgba.push( ~~( alpha * 255 ) ); } return "#" + jQuery.map( rgba, function( v ) { // default to 0 when nulls exist v = ( v || 0 ).toString( 16 ); return v.length === 1 ? "0" + v : v; } ).join( "" ); }, toString: function() { return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); } } ); color.fn.parse.prototype = color.fn; // hsla conversions adapted from: // https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 function hue2rgb( p, q, h ) { h = ( h + 1 ) % 1; if ( h * 6 < 1 ) { return p + ( q - p ) * h * 6; } if ( h * 2 < 1 ) { return q; } if ( h * 3 < 2 ) { return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6; } return p; } spaces.hsla.to = function( rgba ) { if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { return [ null, null, null, rgba[ 3 ] ]; } var r = rgba[ 0 ] / 255, g = rgba[ 1 ] / 255, b = rgba[ 2 ] / 255, a = rgba[ 3 ], max = Math.max( r, g, b ), min = Math.min( r, g, b ), diff = max - min, add = max + min, l = add * 0.5, h, s; if ( min === max ) { h = 0; } else if ( r === max ) { h = ( 60 * ( g - b ) / diff ) + 360; } else if ( g === max ) { h = ( 60 * ( b - r ) / diff ) + 120; } else { h = ( 60 * ( r - g ) / diff ) + 240; } // chroma (diff) == 0 means greyscale which, by definition, saturation = 0% // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) if ( diff === 0 ) { s = 0; } else if ( l <= 0.5 ) { s = diff / add; } else { s = diff / ( 2 - add ); } return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ]; }; spaces.hsla.from = function( hsla ) { if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { return [ null, null, null, hsla[ 3 ] ]; } var h = hsla[ 0 ] / 360, s = hsla[ 1 ], l = hsla[ 2 ], a = hsla[ 3 ], q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, p = 2 * l - q; return [ Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), Math.round( hue2rgb( p, q, h ) * 255 ), Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), a ]; }; each( spaces, function( spaceName, space ) { var props = space.props, cache = space.cache, to = space.to, from = space.from; // makes rgba() and hsla() color.fn[ spaceName ] = function( value ) { // generate a cache for this space if it doesn't exist if ( to && !this[ cache ] ) { this[ cache ] = to( this._rgba ); } if ( value === undefined ) { return this[ cache ].slice(); } var ret, type = getType( value ), arr = ( type === "array" || type === "object" ) ? value : arguments, local = this[ cache ].slice(); each( props, function( key, prop ) { var val = arr[ type === "object" ? key : prop.idx ]; if ( val == null ) { val = local[ prop.idx ]; } local[ prop.idx ] = clamp( val, prop ); } ); if ( from ) { ret = color( from( local ) ); ret[ cache ] = local; return ret; } else { return color( local ); } }; // makes red() green() blue() alpha() hue() saturation() lightness() each( props, function( key, prop ) { // alpha is included in more than one space if ( color.fn[ key ] ) { return; } color.fn[ key ] = function( value ) { var local, cur, match, fn, vtype = getType( value ); if ( key === "alpha" ) { fn = this._hsla ? "hsla" : "rgba"; } else { fn = spaceName; } local = this[ fn ](); cur = local[ prop.idx ]; if ( vtype === "undefined" ) { return cur; } if ( vtype === "function" ) { value = value.call( this, cur ); vtype = getType( value ); } if ( value == null && prop.empty ) { return this; } if ( vtype === "string" ) { match = rplusequals.exec( value ); if ( match ) { value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); } } local[ prop.idx ] = value; return this[ fn ]( local ); }; } ); } ); // add cssHook and .fx.step function for each named hook. // accept a space separated string of properties color.hook = function( hook ) { var hooks = hook.split( " " ); each( hooks, function( _i, hook ) { jQuery.cssHooks[ hook ] = { set: function( elem, value ) { var parsed, curElem, backgroundColor = ""; if ( value !== "transparent" && ( getType( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { value = color( parsed || value ); if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { curElem = hook === "backgroundColor" ? elem.parentNode : elem; while ( ( backgroundColor === "" || backgroundColor === "transparent" ) && curElem && curElem.style ) { try { backgroundColor = jQuery.css( curElem, "backgroundColor" ); curElem = curElem.parentNode; } catch ( e ) { } } value = value.blend( backgroundColor && backgroundColor !== "transparent" ? backgroundColor : "_default" ); } value = value.toRgbaString(); } try { elem.style[ hook ] = value; } catch ( e ) { // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' } } }; jQuery.fx.step[ hook ] = function( fx ) { if ( !fx.colorInit ) { fx.start = color( fx.elem, hook ); fx.end = color( fx.end ); fx.colorInit = true; } jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); }; } ); }; color.hook( stepHooks ); jQuery.cssHooks.borderColor = { expand: function( value ) { var expanded = {}; each( [ "Top", "Right", "Bottom", "Left" ], function( _i, part ) { expanded[ "border" + part + "Color" ] = value; } ); return expanded; } }; // Basic color names only. // Usage of any of the other color names requires adding yourself or including // jquery.color.svg-names.js. colors = jQuery.Color.names = { // 4.1. Basic color keywords aqua: "#00ffff", black: "#000000", blue: "#0000ff", fuchsia: "#ff00ff", gray: "#808080", green: "#008000", lime: "#00ff00", maroon: "#800000", navy: "#000080", olive: "#808000", purple: "#800080", red: "#ff0000", silver: "#c0c0c0", teal: "#008080", white: "#ffffff", yellow: "#ffff00", // 4.2.3. "transparent" color keyword transparent: [ null, null, null, 0 ], _default: "#ffffff" }; /*! * jQuery UI Effects 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Effects Core //>>group: Effects /* eslint-disable max-len */ //>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects. /* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/category/effects-core/ //>>demos: http://jqueryui.com/effect/ var dataSpace = "ui-effects-", dataSpaceStyle = "ui-effects-style", dataSpaceAnimated = "ui-effects-animated"; $.effects = { effect: {} }; /******************************************************************************/ /****************************** CLASS ANIMATIONS ******************************/ /******************************************************************************/ ( function() { var classAnimationActions = [ "add", "remove", "toggle" ], shorthandStyles = { border: 1, borderBottom: 1, borderColor: 1, borderLeft: 1, borderRight: 1, borderTop: 1, borderWidth: 1, margin: 1, padding: 1 }; $.each( [ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) { $.fx.step[ prop ] = function( fx ) { if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { jQuery.style( fx.elem, prop, fx.end ); fx.setAttr = true; } }; } ); function camelCase( string ) { return string.replace( /-([\da-z])/gi, function( all, letter ) { return letter.toUpperCase(); } ); } function getElementStyles( elem ) { var key, len, style = elem.ownerDocument.defaultView ? elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : elem.currentStyle, styles = {}; if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { len = style.length; while ( len-- ) { key = style[ len ]; if ( typeof style[ key ] === "string" ) { styles[ camelCase( key ) ] = style[ key ]; } } // Support: Opera, IE <9 } else { for ( key in style ) { if ( typeof style[ key ] === "string" ) { styles[ key ] = style[ key ]; } } } return styles; } function styleDifference( oldStyle, newStyle ) { var diff = {}, name, value; for ( name in newStyle ) { value = newStyle[ name ]; if ( oldStyle[ name ] !== value ) { if ( !shorthandStyles[ name ] ) { if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { diff[ name ] = value; } } } } return diff; } // Support: jQuery <1.8 if ( !$.fn.addBack ) { $.fn.addBack = function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); }; } $.effects.animateClass = function( value, duration, easing, callback ) { var o = $.speed( duration, easing, callback ); return this.queue( function() { var animated = $( this ), baseClass = animated.attr( "class" ) || "", applyClassChange, allAnimations = o.children ? animated.find( "*" ).addBack() : animated; // Map the animated objects to store the original styles. allAnimations = allAnimations.map( function() { var el = $( this ); return { el: el, start: getElementStyles( this ) }; } ); // Apply class change applyClassChange = function() { $.each( classAnimationActions, function( i, action ) { if ( value[ action ] ) { animated[ action + "Class" ]( value[ action ] ); } } ); }; applyClassChange(); // Map all animated objects again - calculate new styles and diff allAnimations = allAnimations.map( function() { this.end = getElementStyles( this.el[ 0 ] ); this.diff = styleDifference( this.start, this.end ); return this; } ); // Apply original class animated.attr( "class", baseClass ); // Map all animated objects again - this time collecting a promise allAnimations = allAnimations.map( function() { var styleInfo = this, dfd = $.Deferred(), opts = $.extend( {}, o, { queue: false, complete: function() { dfd.resolve( styleInfo ); } } ); this.el.animate( this.diff, opts ); return dfd.promise(); } ); // Once all animations have completed: $.when.apply( $, allAnimations.get() ).done( function() { // Set the final class applyClassChange(); // For each animated element, // clear all css properties that were animated $.each( arguments, function() { var el = this.el; $.each( this.diff, function( key ) { el.css( key, "" ); } ); } ); // This is guarnteed to be there if you use jQuery.speed() // it also handles dequeuing the next anim... o.complete.call( animated[ 0 ] ); } ); } ); }; $.fn.extend( { addClass: ( function( orig ) { return function( classNames, speed, easing, callback ) { return speed ? $.effects.animateClass.call( this, { add: classNames }, speed, easing, callback ) : orig.apply( this, arguments ); }; } )( $.fn.addClass ), removeClass: ( function( orig ) { return function( classNames, speed, easing, callback ) { return arguments.length > 1 ? $.effects.animateClass.call( this, { remove: classNames }, speed, easing, callback ) : orig.apply( this, arguments ); }; } )( $.fn.removeClass ), toggleClass: ( function( orig ) { return function( classNames, force, speed, easing, callback ) { if ( typeof force === "boolean" || force === undefined ) { if ( !speed ) { // Without speed parameter return orig.apply( this, arguments ); } else { return $.effects.animateClass.call( this, ( force ? { add: classNames } : { remove: classNames } ), speed, easing, callback ); } } else { // Without force parameter return $.effects.animateClass.call( this, { toggle: classNames }, force, speed, easing ); } }; } )( $.fn.toggleClass ), switchClass: function( remove, add, speed, easing, callback ) { return $.effects.animateClass.call( this, { add: add, remove: remove }, speed, easing, callback ); } } ); } )(); /******************************************************************************/ /*********************************** EFFECTS **********************************/ /******************************************************************************/ ( function() { if ( $.expr && $.expr.pseudos && $.expr.pseudos.animated ) { $.expr.pseudos.animated = ( function( orig ) { return function( elem ) { return !!$( elem ).data( dataSpaceAnimated ) || orig( elem ); }; } )( $.expr.pseudos.animated ); } if ( $.uiBackCompat !== false ) { $.extend( $.effects, { // Saves a set of properties in a data storage save: function( element, set ) { var i = 0, length = set.length; for ( ; i < length; i++ ) { if ( set[ i ] !== null ) { element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); } } }, // Restores a set of previously saved properties from a data storage restore: function( element, set ) { var val, i = 0, length = set.length; for ( ; i < length; i++ ) { if ( set[ i ] !== null ) { val = element.data( dataSpace + set[ i ] ); element.css( set[ i ], val ); } } }, setMode: function( el, mode ) { if ( mode === "toggle" ) { mode = el.is( ":hidden" ) ? "show" : "hide"; } return mode; }, // Wraps the element around a wrapper that copies position properties createWrapper: function( element ) { // If the element is already wrapped, return it if ( element.parent().is( ".ui-effects-wrapper" ) ) { return element.parent(); } // Wrap the element var props = { width: element.outerWidth( true ), height: element.outerHeight( true ), "float": element.css( "float" ) }, wrapper = $( "

" ) .addClass( "ui-effects-wrapper" ) .css( { fontSize: "100%", background: "transparent", border: "none", margin: 0, padding: 0 } ), // Store the size in case width/height are defined in % - Fixes #5245 size = { width: element.width(), height: element.height() }, active = document.activeElement; // Support: Firefox // Firefox incorrectly exposes anonymous content // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 try { // eslint-disable-next-line no-unused-expressions active.id; } catch ( e ) { active = document.body; } element.wrap( wrapper ); // Fixes #7595 - Elements lose focus when wrapped. if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { $( active ).trigger( "focus" ); } // Hotfix for jQuery 1.4 since some change in wrap() seems to actually // lose the reference to the wrapped element wrapper = element.parent(); // Transfer positioning properties to the wrapper if ( element.css( "position" ) === "static" ) { wrapper.css( { position: "relative" } ); element.css( { position: "relative" } ); } else { $.extend( props, { position: element.css( "position" ), zIndex: element.css( "z-index" ) } ); $.each( [ "top", "left", "bottom", "right" ], function( i, pos ) { props[ pos ] = element.css( pos ); if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { props[ pos ] = "auto"; } } ); element.css( { position: "relative", top: 0, left: 0, right: "auto", bottom: "auto" } ); } element.css( size ); return wrapper.css( props ).show(); }, removeWrapper: function( element ) { var active = document.activeElement; if ( element.parent().is( ".ui-effects-wrapper" ) ) { element.parent().replaceWith( element ); // Fixes #7595 - Elements lose focus when wrapped. if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { $( active ).trigger( "focus" ); } } return element; } } ); } $.extend( $.effects, { version: "1.13.0", define: function( name, mode, effect ) { if ( !effect ) { effect = mode; mode = "effect"; } $.effects.effect[ name ] = effect; $.effects.effect[ name ].mode = mode; return effect; }, scaledDimensions: function( element, percent, direction ) { if ( percent === 0 ) { return { height: 0, width: 0, outerHeight: 0, outerWidth: 0 }; } var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1, y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1; return { height: element.height() * y, width: element.width() * x, outerHeight: element.outerHeight() * y, outerWidth: element.outerWidth() * x }; }, clipToBox: function( animation ) { return { width: animation.clip.right - animation.clip.left, height: animation.clip.bottom - animation.clip.top, left: animation.clip.left, top: animation.clip.top }; }, // Injects recently queued functions to be first in line (after "inprogress") unshift: function( element, queueLength, count ) { var queue = element.queue(); if ( queueLength > 1 ) { queue.splice.apply( queue, [ 1, 0 ].concat( queue.splice( queueLength, count ) ) ); } element.dequeue(); }, saveStyle: function( element ) { element.data( dataSpaceStyle, element[ 0 ].style.cssText ); }, restoreStyle: function( element ) { element[ 0 ].style.cssText = element.data( dataSpaceStyle ) || ""; element.removeData( dataSpaceStyle ); }, mode: function( element, mode ) { var hidden = element.is( ":hidden" ); if ( mode === "toggle" ) { mode = hidden ? "show" : "hide"; } if ( hidden ? mode === "hide" : mode === "show" ) { mode = "none"; } return mode; }, // Translates a [top,left] array into a baseline value getBaseline: function( origin, original ) { var y, x; switch ( origin[ 0 ] ) { case "top": y = 0; break; case "middle": y = 0.5; break; case "bottom": y = 1; break; default: y = origin[ 0 ] / original.height; } switch ( origin[ 1 ] ) { case "left": x = 0; break; case "center": x = 0.5; break; case "right": x = 1; break; default: x = origin[ 1 ] / original.width; } return { x: x, y: y }; }, // Creates a placeholder element so that the original element can be made absolute createPlaceholder: function( element ) { var placeholder, cssPosition = element.css( "position" ), position = element.position(); // Lock in margins first to account for form elements, which // will change margin if you explicitly set height // see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380 // Support: Safari element.css( { marginTop: element.css( "marginTop" ), marginBottom: element.css( "marginBottom" ), marginLeft: element.css( "marginLeft" ), marginRight: element.css( "marginRight" ) } ) .outerWidth( element.outerWidth() ) .outerHeight( element.outerHeight() ); if ( /^(static|relative)/.test( cssPosition ) ) { cssPosition = "absolute"; placeholder = $( "<" + element[ 0 ].nodeName + ">" ).insertAfter( element ).css( { // Convert inline to inline block to account for inline elements // that turn to inline block based on content (like img) display: /^(inline|ruby)/.test( element.css( "display" ) ) ? "inline-block" : "block", visibility: "hidden", // Margins need to be set to account for margin collapse marginTop: element.css( "marginTop" ), marginBottom: element.css( "marginBottom" ), marginLeft: element.css( "marginLeft" ), marginRight: element.css( "marginRight" ), "float": element.css( "float" ) } ) .outerWidth( element.outerWidth() ) .outerHeight( element.outerHeight() ) .addClass( "ui-effects-placeholder" ); element.data( dataSpace + "placeholder", placeholder ); } element.css( { position: cssPosition, left: position.left, top: position.top } ); return placeholder; }, removePlaceholder: function( element ) { var dataKey = dataSpace + "placeholder", placeholder = element.data( dataKey ); if ( placeholder ) { placeholder.remove(); element.removeData( dataKey ); } }, // Removes a placeholder if it exists and restores // properties that were modified during placeholder creation cleanUp: function( element ) { $.effects.restoreStyle( element ); $.effects.removePlaceholder( element ); }, setTransition: function( element, list, factor, value ) { value = value || {}; $.each( list, function( i, x ) { var unit = element.cssUnit( x ); if ( unit[ 0 ] > 0 ) { value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; } } ); return value; } } ); // Return an effect options object for the given parameters: function _normalizeArguments( effect, options, speed, callback ) { // Allow passing all options as the first parameter if ( $.isPlainObject( effect ) ) { options = effect; effect = effect.effect; } // Convert to an object effect = { effect: effect }; // Catch (effect, null, ...) if ( options == null ) { options = {}; } // Catch (effect, callback) if ( typeof options === "function" ) { callback = options; speed = null; options = {}; } // Catch (effect, speed, ?) if ( typeof options === "number" || $.fx.speeds[ options ] ) { callback = speed; speed = options; options = {}; } // Catch (effect, options, callback) if ( typeof speed === "function" ) { callback = speed; speed = null; } // Add options to effect if ( options ) { $.extend( effect, options ); } speed = speed || options.duration; effect.duration = $.fx.off ? 0 : typeof speed === "number" ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default; effect.complete = callback || options.complete; return effect; } function standardAnimationOption( option ) { // Valid standard speeds (nothing, number, named speed) if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) { return true; } // Invalid strings - treat as "normal" speed if ( typeof option === "string" && !$.effects.effect[ option ] ) { return true; } // Complete callback if ( typeof option === "function" ) { return true; } // Options hash (but not naming an effect) if ( typeof option === "object" && !option.effect ) { return true; } // Didn't match any standard API return false; } $.fn.extend( { effect: function( /* effect, options, speed, callback */ ) { var args = _normalizeArguments.apply( this, arguments ), effectMethod = $.effects.effect[ args.effect ], defaultMode = effectMethod.mode, queue = args.queue, queueName = queue || "fx", complete = args.complete, mode = args.mode, modes = [], prefilter = function( next ) { var el = $( this ), normalizedMode = $.effects.mode( el, mode ) || defaultMode; // Sentinel for duck-punching the :animated pseudo-selector el.data( dataSpaceAnimated, true ); // Save effect mode for later use, // we can't just call $.effects.mode again later, // as the .show() below destroys the initial state modes.push( normalizedMode ); // See $.uiBackCompat inside of run() for removal of defaultMode in 1.14 if ( defaultMode && ( normalizedMode === "show" || ( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) { el.show(); } if ( !defaultMode || normalizedMode !== "none" ) { $.effects.saveStyle( el ); } if ( typeof next === "function" ) { next(); } }; if ( $.fx.off || !effectMethod ) { // Delegate to the original method (e.g., .show()) if possible if ( mode ) { return this[ mode ]( args.duration, complete ); } else { return this.each( function() { if ( complete ) { complete.call( this ); } } ); } } function run( next ) { var elem = $( this ); function cleanup() { elem.removeData( dataSpaceAnimated ); $.effects.cleanUp( elem ); if ( args.mode === "hide" ) { elem.hide(); } done(); } function done() { if ( typeof complete === "function" ) { complete.call( elem[ 0 ] ); } if ( typeof next === "function" ) { next(); } } // Override mode option on a per element basis, // as toggle can be either show or hide depending on element state args.mode = modes.shift(); if ( $.uiBackCompat !== false && !defaultMode ) { if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { // Call the core method to track "olddisplay" properly elem[ mode ](); done(); } else { effectMethod.call( elem[ 0 ], args, done ); } } else { if ( args.mode === "none" ) { // Call the core method to track "olddisplay" properly elem[ mode ](); done(); } else { effectMethod.call( elem[ 0 ], args, cleanup ); } } } // Run prefilter on all elements first to ensure that // any showing or hiding happens before placeholder creation, // which ensures that any layout changes are correctly captured. return queue === false ? this.each( prefilter ).each( run ) : this.queue( queueName, prefilter ).queue( queueName, run ); }, show: ( function( orig ) { return function( option ) { if ( standardAnimationOption( option ) ) { return orig.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "show"; return this.effect.call( this, args ); } }; } )( $.fn.show ), hide: ( function( orig ) { return function( option ) { if ( standardAnimationOption( option ) ) { return orig.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "hide"; return this.effect.call( this, args ); } }; } )( $.fn.hide ), toggle: ( function( orig ) { return function( option ) { if ( standardAnimationOption( option ) || typeof option === "boolean" ) { return orig.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "toggle"; return this.effect.call( this, args ); } }; } )( $.fn.toggle ), cssUnit: function( key ) { var style = this.css( key ), val = []; $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { if ( style.indexOf( unit ) > 0 ) { val = [ parseFloat( style ), unit ]; } } ); return val; }, cssClip: function( clipObj ) { if ( clipObj ) { return this.css( "clip", "rect(" + clipObj.top + "px " + clipObj.right + "px " + clipObj.bottom + "px " + clipObj.left + "px)" ); } return parseClip( this.css( "clip" ), this ); }, transfer: function( options, done ) { var element = $( this ), target = $( options.to ), targetFixed = target.css( "position" ) === "fixed", body = $( "body" ), fixTop = targetFixed ? body.scrollTop() : 0, fixLeft = targetFixed ? body.scrollLeft() : 0, endPosition = target.offset(), animation = { top: endPosition.top - fixTop, left: endPosition.left - fixLeft, height: target.innerHeight(), width: target.innerWidth() }, startPosition = element.offset(), transfer = $( "
" ); transfer .appendTo( "body" ) .addClass( options.className ) .css( { top: startPosition.top - fixTop, left: startPosition.left - fixLeft, height: element.innerHeight(), width: element.innerWidth(), position: targetFixed ? "fixed" : "absolute" } ) .animate( animation, options.duration, options.easing, function() { transfer.remove(); if ( typeof done === "function" ) { done(); } } ); } } ); function parseClip( str, element ) { var outerWidth = element.outerWidth(), outerHeight = element.outerHeight(), clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/, values = clipRegex.exec( str ) || [ "", 0, outerWidth, outerHeight, 0 ]; return { top: parseFloat( values[ 1 ] ) || 0, right: values[ 2 ] === "auto" ? outerWidth : parseFloat( values[ 2 ] ), bottom: values[ 3 ] === "auto" ? outerHeight : parseFloat( values[ 3 ] ), left: parseFloat( values[ 4 ] ) || 0 }; } $.fx.step.clip = function( fx ) { if ( !fx.clipInit ) { fx.start = $( fx.elem ).cssClip(); if ( typeof fx.end === "string" ) { fx.end = parseClip( fx.end, fx.elem ); } fx.clipInit = true; } $( fx.elem ).cssClip( { top: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top, right: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right, bottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom, left: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left } ); }; } )(); /******************************************************************************/ /*********************************** EASING ***********************************/ /******************************************************************************/ ( function() { // Based on easing equations from Robert Penner (http://www.robertpenner.com/easing) var baseEasings = {}; $.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { baseEasings[ name ] = function( p ) { return Math.pow( p, i + 2 ); }; } ); $.extend( baseEasings, { Sine: function( p ) { return 1 - Math.cos( p * Math.PI / 2 ); }, Circ: function( p ) { return 1 - Math.sqrt( 1 - p * p ); }, Elastic: function( p ) { return p === 0 || p === 1 ? p : -Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 ); }, Back: function( p ) { return p * p * ( 3 * p - 2 ); }, Bounce: function( p ) { var pow2, bounce = 4; while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); } } ); $.each( baseEasings, function( name, easeIn ) { $.easing[ "easeIn" + name ] = easeIn; $.easing[ "easeOut" + name ] = function( p ) { return 1 - easeIn( 1 - p ); }; $.easing[ "easeInOut" + name ] = function( p ) { return p < 0.5 ? easeIn( p * 2 ) / 2 : 1 - easeIn( p * -2 + 2 ) / 2; }; } ); } )(); var effect = $.effects; /*! * jQuery UI Effects Blind 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Blind Effect //>>group: Effects //>>description: Blinds the element. //>>docs: http://api.jqueryui.com/blind-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, done ) { var map = { up: [ "bottom", "top" ], vertical: [ "bottom", "top" ], down: [ "top", "bottom" ], left: [ "right", "left" ], horizontal: [ "right", "left" ], right: [ "left", "right" ] }, element = $( this ), direction = options.direction || "up", start = element.cssClip(), animate = { clip: $.extend( {}, start ) }, placeholder = $.effects.createPlaceholder( element ); animate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ]; if ( options.mode === "show" ) { element.cssClip( animate.clip ); if ( placeholder ) { placeholder.css( $.effects.clipToBox( animate ) ); } animate.clip = start; } if ( placeholder ) { placeholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing ); } element.animate( animate, { queue: false, duration: options.duration, easing: options.easing, complete: done } ); } ); /*! * jQuery UI Effects Bounce 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Bounce Effect //>>group: Effects //>>description: Bounces an element horizontally or vertically n times. //>>docs: http://api.jqueryui.com/bounce-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectBounce = $.effects.define( "bounce", function( options, done ) { var upAnim, downAnim, refValue, element = $( this ), // Defaults: mode = options.mode, hide = mode === "hide", show = mode === "show", direction = options.direction || "up", distance = options.distance, times = options.times || 5, // Number of internal animations anims = times * 2 + ( show || hide ? 1 : 0 ), speed = options.duration / anims, easing = options.easing, // Utility: ref = ( direction === "up" || direction === "down" ) ? "top" : "left", motion = ( direction === "up" || direction === "left" ), i = 0, queuelen = element.queue().length; $.effects.createPlaceholder( element ); refValue = element.css( ref ); // Default distance for the BIGGEST bounce is the outer Distance / 3 if ( !distance ) { distance = element[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; } if ( show ) { downAnim = { opacity: 1 }; downAnim[ ref ] = refValue; // If we are showing, force opacity 0 and set the initial position // then do the "first" animation element .css( "opacity", 0 ) .css( ref, motion ? -distance * 2 : distance * 2 ) .animate( downAnim, speed, easing ); } // Start at the smallest distance if we are hiding if ( hide ) { distance = distance / Math.pow( 2, times - 1 ); } downAnim = {}; downAnim[ ref ] = refValue; // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here for ( ; i < times; i++ ) { upAnim = {}; upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; element .animate( upAnim, speed, easing ) .animate( downAnim, speed, easing ); distance = hide ? distance * 2 : distance / 2; } // Last Bounce when Hiding if ( hide ) { upAnim = { opacity: 0 }; upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; element.animate( upAnim, speed, easing ); } element.queue( done ); $.effects.unshift( element, queuelen, anims + 1 ); } ); /*! * jQuery UI Effects Clip 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Clip Effect //>>group: Effects //>>description: Clips the element on and off like an old TV. //>>docs: http://api.jqueryui.com/clip-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectClip = $.effects.define( "clip", "hide", function( options, done ) { var start, animate = {}, element = $( this ), direction = options.direction || "vertical", both = direction === "both", horizontal = both || direction === "horizontal", vertical = both || direction === "vertical"; start = element.cssClip(); animate.clip = { top: vertical ? ( start.bottom - start.top ) / 2 : start.top, right: horizontal ? ( start.right - start.left ) / 2 : start.right, bottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom, left: horizontal ? ( start.right - start.left ) / 2 : start.left }; $.effects.createPlaceholder( element ); if ( options.mode === "show" ) { element.cssClip( animate.clip ); animate.clip = start; } element.animate( animate, { queue: false, duration: options.duration, easing: options.easing, complete: done } ); } ); /*! * jQuery UI Effects Drop 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Drop Effect //>>group: Effects //>>description: Moves an element in one direction and hides it at the same time. //>>docs: http://api.jqueryui.com/drop-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, done ) { var distance, element = $( this ), mode = options.mode, show = mode === "show", direction = options.direction || "left", ref = ( direction === "up" || direction === "down" ) ? "top" : "left", motion = ( direction === "up" || direction === "left" ) ? "-=" : "+=", oppositeMotion = ( motion === "+=" ) ? "-=" : "+=", animation = { opacity: 0 }; $.effects.createPlaceholder( element ); distance = options.distance || element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2; animation[ ref ] = motion + distance; if ( show ) { element.css( animation ); animation[ ref ] = oppositeMotion + distance; animation.opacity = 1; } // Animate element.animate( animation, { queue: false, duration: options.duration, easing: options.easing, complete: done } ); } ); /*! * jQuery UI Effects Explode 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Explode Effect //>>group: Effects /* eslint-disable max-len */ //>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness. /* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/explode-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectExplode = $.effects.define( "explode", "hide", function( options, done ) { var i, j, left, top, mx, my, rows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3, cells = rows, element = $( this ), mode = options.mode, show = mode === "show", // Show and then visibility:hidden the element before calculating offset offset = element.show().css( "visibility", "hidden" ).offset(), // Width and height of a piece width = Math.ceil( element.outerWidth() / cells ), height = Math.ceil( element.outerHeight() / rows ), pieces = []; // Children animate complete: function childComplete() { pieces.push( this ); if ( pieces.length === rows * cells ) { animComplete(); } } // Clone the element for each row and cell. for ( i = 0; i < rows; i++ ) { // ===> top = offset.top + i * height; my = i - ( rows - 1 ) / 2; for ( j = 0; j < cells; j++ ) { // ||| left = offset.left + j * width; mx = j - ( cells - 1 ) / 2; // Create a clone of the now hidden main element that will be absolute positioned // within a wrapper div off the -left and -top equal to size of our pieces element .clone() .appendTo( "body" ) .wrap( "
" ) .css( { position: "absolute", visibility: "visible", left: -j * width, top: -i * height } ) // Select the wrapper - make it overflow: hidden and absolute positioned based on // where the original was located +left and +top equal to the size of pieces .parent() .addClass( "ui-effects-explode" ) .css( { position: "absolute", overflow: "hidden", width: width, height: height, left: left + ( show ? mx * width : 0 ), top: top + ( show ? my * height : 0 ), opacity: show ? 0 : 1 } ) .animate( { left: left + ( show ? 0 : mx * width ), top: top + ( show ? 0 : my * height ), opacity: show ? 1 : 0 }, options.duration || 500, options.easing, childComplete ); } } function animComplete() { element.css( { visibility: "visible" } ); $( pieces ).remove(); done(); } } ); /*! * jQuery UI Effects Fade 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Fade Effect //>>group: Effects //>>description: Fades the element. //>>docs: http://api.jqueryui.com/fade-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, done ) { var show = options.mode === "show"; $( this ) .css( "opacity", show ? 0 : 1 ) .animate( { opacity: show ? 1 : 0 }, { queue: false, duration: options.duration, easing: options.easing, complete: done } ); } ); /*! * jQuery UI Effects Fold 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Fold Effect //>>group: Effects //>>description: Folds an element first horizontally and then vertically. //>>docs: http://api.jqueryui.com/fold-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectFold = $.effects.define( "fold", "hide", function( options, done ) { // Create element var element = $( this ), mode = options.mode, show = mode === "show", hide = mode === "hide", size = options.size || 15, percent = /([0-9]+)%/.exec( size ), horizFirst = !!options.horizFirst, ref = horizFirst ? [ "right", "bottom" ] : [ "bottom", "right" ], duration = options.duration / 2, placeholder = $.effects.createPlaceholder( element ), start = element.cssClip(), animation1 = { clip: $.extend( {}, start ) }, animation2 = { clip: $.extend( {}, start ) }, distance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ], queuelen = element.queue().length; if ( percent ) { size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; } animation1.clip[ ref[ 0 ] ] = size; animation2.clip[ ref[ 0 ] ] = size; animation2.clip[ ref[ 1 ] ] = 0; if ( show ) { element.cssClip( animation2.clip ); if ( placeholder ) { placeholder.css( $.effects.clipToBox( animation2 ) ); } animation2.clip = start; } // Animate element .queue( function( next ) { if ( placeholder ) { placeholder .animate( $.effects.clipToBox( animation1 ), duration, options.easing ) .animate( $.effects.clipToBox( animation2 ), duration, options.easing ); } next(); } ) .animate( animation1, duration, options.easing ) .animate( animation2, duration, options.easing ) .queue( done ); $.effects.unshift( element, queuelen, 4 ); } ); /*! * jQuery UI Effects Highlight 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Highlight Effect //>>group: Effects //>>description: Highlights the background of an element in a defined color for a custom duration. //>>docs: http://api.jqueryui.com/highlight-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectHighlight = $.effects.define( "highlight", "show", function( options, done ) { var element = $( this ), animation = { backgroundColor: element.css( "backgroundColor" ) }; if ( options.mode === "hide" ) { animation.opacity = 0; } $.effects.saveStyle( element ); element .css( { backgroundImage: "none", backgroundColor: options.color || "#ffff99" } ) .animate( animation, { queue: false, duration: options.duration, easing: options.easing, complete: done } ); } ); /*! * jQuery UI Effects Size 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Size Effect //>>group: Effects //>>description: Resize an element to a specified width and height. //>>docs: http://api.jqueryui.com/size-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectSize = $.effects.define( "size", function( options, done ) { // Create element var baseline, factor, temp, element = $( this ), // Copy for children cProps = [ "fontSize" ], vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], // Set options mode = options.mode, restore = mode !== "effect", scale = options.scale || "both", origin = options.origin || [ "middle", "center" ], position = element.css( "position" ), pos = element.position(), original = $.effects.scaledDimensions( element ), from = options.from || original, to = options.to || $.effects.scaledDimensions( element, 0 ); $.effects.createPlaceholder( element ); if ( mode === "show" ) { temp = from; from = to; to = temp; } // Set scaling factor factor = { from: { y: from.height / original.height, x: from.width / original.width }, to: { y: to.height / original.height, x: to.width / original.width } }; // Scale the css box if ( scale === "box" || scale === "both" ) { // Vertical props scaling if ( factor.from.y !== factor.to.y ) { from = $.effects.setTransition( element, vProps, factor.from.y, from ); to = $.effects.setTransition( element, vProps, factor.to.y, to ); } // Horizontal props scaling if ( factor.from.x !== factor.to.x ) { from = $.effects.setTransition( element, hProps, factor.from.x, from ); to = $.effects.setTransition( element, hProps, factor.to.x, to ); } } // Scale the content if ( scale === "content" || scale === "both" ) { // Vertical props scaling if ( factor.from.y !== factor.to.y ) { from = $.effects.setTransition( element, cProps, factor.from.y, from ); to = $.effects.setTransition( element, cProps, factor.to.y, to ); } } // Adjust the position properties based on the provided origin points if ( origin ) { baseline = $.effects.getBaseline( origin, original ); from.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top; from.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left; to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top; to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left; } delete from.outerHeight; delete from.outerWidth; element.css( from ); // Animate the children if desired if ( scale === "content" || scale === "both" ) { vProps = vProps.concat( [ "marginTop", "marginBottom" ] ).concat( cProps ); hProps = hProps.concat( [ "marginLeft", "marginRight" ] ); // Only animate children with width attributes specified // TODO: is this right? should we include anything with css width specified as well element.find( "*[width]" ).each( function() { var child = $( this ), childOriginal = $.effects.scaledDimensions( child ), childFrom = { height: childOriginal.height * factor.from.y, width: childOriginal.width * factor.from.x, outerHeight: childOriginal.outerHeight * factor.from.y, outerWidth: childOriginal.outerWidth * factor.from.x }, childTo = { height: childOriginal.height * factor.to.y, width: childOriginal.width * factor.to.x, outerHeight: childOriginal.height * factor.to.y, outerWidth: childOriginal.width * factor.to.x }; // Vertical props scaling if ( factor.from.y !== factor.to.y ) { childFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom ); childTo = $.effects.setTransition( child, vProps, factor.to.y, childTo ); } // Horizontal props scaling if ( factor.from.x !== factor.to.x ) { childFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom ); childTo = $.effects.setTransition( child, hProps, factor.to.x, childTo ); } if ( restore ) { $.effects.saveStyle( child ); } // Animate children child.css( childFrom ); child.animate( childTo, options.duration, options.easing, function() { // Restore children if ( restore ) { $.effects.restoreStyle( child ); } } ); } ); } // Animate element.animate( to, { queue: false, duration: options.duration, easing: options.easing, complete: function() { var offset = element.offset(); if ( to.opacity === 0 ) { element.css( "opacity", from.opacity ); } if ( !restore ) { element .css( "position", position === "static" ? "relative" : position ) .offset( offset ); // Need to save style here so that automatic style restoration // doesn't restore to the original styles from before the animation. $.effects.saveStyle( element ); } done(); } } ); } ); /*! * jQuery UI Effects Scale 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Scale Effect //>>group: Effects //>>description: Grows or shrinks an element and its content. //>>docs: http://api.jqueryui.com/scale-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectScale = $.effects.define( "scale", function( options, done ) { // Create element var el = $( this ), mode = options.mode, percent = parseInt( options.percent, 10 ) || ( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== "effect" ? 0 : 100 ) ), newOptions = $.extend( true, { from: $.effects.scaledDimensions( el ), to: $.effects.scaledDimensions( el, percent, options.direction || "both" ), origin: options.origin || [ "middle", "center" ] }, options ); // Fade option to support puff if ( options.fade ) { newOptions.from.opacity = 1; newOptions.to.opacity = 0; } $.effects.effect.size.call( this, newOptions, done ); } ); /*! * jQuery UI Effects Puff 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Puff Effect //>>group: Effects //>>description: Creates a puff effect by scaling the element up and hiding it at the same time. //>>docs: http://api.jqueryui.com/puff-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, done ) { var newOptions = $.extend( true, {}, options, { fade: true, percent: parseInt( options.percent, 10 ) || 150 } ); $.effects.effect.scale.call( this, newOptions, done ); } ); /*! * jQuery UI Effects Pulsate 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Pulsate Effect //>>group: Effects //>>description: Pulsates an element n times by changing the opacity to zero and back. //>>docs: http://api.jqueryui.com/pulsate-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( options, done ) { var element = $( this ), mode = options.mode, show = mode === "show", hide = mode === "hide", showhide = show || hide, // Showing or hiding leaves off the "last" animation anims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), duration = options.duration / anims, animateTo = 0, i = 1, queuelen = element.queue().length; if ( show || !element.is( ":visible" ) ) { element.css( "opacity", 0 ).show(); animateTo = 1; } // Anims - 1 opacity "toggles" for ( ; i < anims; i++ ) { element.animate( { opacity: animateTo }, duration, options.easing ); animateTo = 1 - animateTo; } element.animate( { opacity: animateTo }, duration, options.easing ); element.queue( done ); $.effects.unshift( element, queuelen, anims + 1 ); } ); /*! * jQuery UI Effects Shake 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Shake Effect //>>group: Effects //>>description: Shakes an element horizontally or vertically n times. //>>docs: http://api.jqueryui.com/shake-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectShake = $.effects.define( "shake", function( options, done ) { var i = 1, element = $( this ), direction = options.direction || "left", distance = options.distance || 20, times = options.times || 3, anims = times * 2 + 1, speed = Math.round( options.duration / anims ), ref = ( direction === "up" || direction === "down" ) ? "top" : "left", positiveMotion = ( direction === "up" || direction === "left" ), animation = {}, animation1 = {}, animation2 = {}, queuelen = element.queue().length; $.effects.createPlaceholder( element ); // Animation animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2; animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; // Animate element.animate( animation, speed, options.easing ); // Shakes for ( ; i < times; i++ ) { element .animate( animation1, speed, options.easing ) .animate( animation2, speed, options.easing ); } element .animate( animation1, speed, options.easing ) .animate( animation, speed / 2, options.easing ) .queue( done ); $.effects.unshift( element, queuelen, anims + 1 ); } ); /*! * jQuery UI Effects Slide 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Slide Effect //>>group: Effects //>>description: Slides an element in and out of the viewport. //>>docs: http://api.jqueryui.com/slide-effect/ //>>demos: http://jqueryui.com/effect/ var effectsEffectSlide = $.effects.define( "slide", "show", function( options, done ) { var startClip, startRef, element = $( this ), map = { up: [ "bottom", "top" ], down: [ "top", "bottom" ], left: [ "right", "left" ], right: [ "left", "right" ] }, mode = options.mode, direction = options.direction || "left", ref = ( direction === "up" || direction === "down" ) ? "top" : "left", positiveMotion = ( direction === "up" || direction === "left" ), distance = options.distance || element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ), animation = {}; $.effects.createPlaceholder( element ); startClip = element.cssClip(); startRef = element.position()[ ref ]; // Define hide animation animation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef; animation.clip = element.cssClip(); animation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ]; // Reverse the animation if we're showing if ( mode === "show" ) { element.cssClip( animation.clip ); element.css( ref, animation[ ref ] ); animation.clip = startClip; animation[ ref ] = startRef; } // Actually animate element.animate( animation, { queue: false, duration: options.duration, easing: options.easing, complete: done } ); } ); /*! * jQuery UI Effects Transfer 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Transfer Effect //>>group: Effects //>>description: Displays a transfer effect from one element to another. //>>docs: http://api.jqueryui.com/transfer-effect/ //>>demos: http://jqueryui.com/effect/ var effect; if ( $.uiBackCompat !== false ) { effect = $.effects.define( "transfer", function( options, done ) { $( this ).transfer( options, done ); } ); } var effectsEffectTransfer = effect; /*! * jQuery UI Focusable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: :focusable Selector //>>group: Core //>>description: Selects elements which can be focused. //>>docs: http://api.jqueryui.com/focusable-selector/ // Selectors $.ui.focusable = function( element, hasTabindex ) { var map, mapName, img, focusableIfVisible, fieldset, nodeName = element.nodeName.toLowerCase(); if ( "area" === nodeName ) { map = element.parentNode; mapName = map.name; if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { return false; } img = $( "img[usemap='#" + mapName + "']" ); return img.length > 0 && img.is( ":visible" ); } if ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) { focusableIfVisible = !element.disabled; if ( focusableIfVisible ) { // Form controls within a disabled fieldset are disabled. // However, controls within the fieldset's legend do not get disabled. // Since controls generally aren't placed inside legends, we skip // this portion of the check. fieldset = $( element ).closest( "fieldset" )[ 0 ]; if ( fieldset ) { focusableIfVisible = !fieldset.disabled; } } } else if ( "a" === nodeName ) { focusableIfVisible = element.href || hasTabindex; } else { focusableIfVisible = hasTabindex; } return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) ); }; // Support: IE 8 only // IE 8 doesn't resolve inherit to visible/hidden for computed values function visible( element ) { var visibility = element.css( "visibility" ); while ( visibility === "inherit" ) { element = element.parent(); visibility = element.css( "visibility" ); } return visibility === "visible"; } $.extend( $.expr.pseudos, { focusable: function( element ) { return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); } } ); var focusable = $.ui.focusable; // Support: IE8 Only // IE8 does not support the form attribute and when it is supplied. It overwrites the form prop // with a string, so we need to find the proper form. var form = $.fn._form = function() { return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); }; /*! * jQuery UI Form Reset Mixin 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Form Reset Mixin //>>group: Core //>>description: Refresh input widgets when their form is reset //>>docs: http://api.jqueryui.com/form-reset-mixin/ var formResetMixin = $.ui.formResetMixin = { _formResetHandler: function() { var form = $( this ); // Wait for the form reset to actually happen before refreshing setTimeout( function() { var instances = form.data( "ui-form-reset-instances" ); $.each( instances, function() { this.refresh(); } ); } ); }, _bindFormResetHandler: function() { this.form = this.element._form(); if ( !this.form.length ) { return; } var instances = this.form.data( "ui-form-reset-instances" ) || []; if ( !instances.length ) { // We don't use _on() here because we use a single event handler per form this.form.on( "reset.ui-form-reset", this._formResetHandler ); } instances.push( this ); this.form.data( "ui-form-reset-instances", instances ); }, _unbindFormResetHandler: function() { if ( !this.form.length ) { return; } var instances = this.form.data( "ui-form-reset-instances" ); instances.splice( $.inArray( this, instances ), 1 ); if ( instances.length ) { this.form.data( "ui-form-reset-instances", instances ); } else { this.form .removeData( "ui-form-reset-instances" ) .off( "reset.ui-form-reset" ); } } }; /*! * jQuery UI Support for jQuery core 1.8.x and newer 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * */ //>>label: jQuery 1.8+ Support //>>group: Core //>>description: Support version 1.8.x and newer of jQuery core // Support: jQuery 1.9.x or older // $.expr[ ":" ] is deprecated. if ( !$.expr.pseudos ) { $.expr.pseudos = $.expr[ ":" ]; } // Support: jQuery 1.11.x or older // $.unique has been renamed to $.uniqueSort if ( !$.uniqueSort ) { $.uniqueSort = $.unique; } // Support: jQuery 2.2.x or older. // This method has been defined in jQuery 3.0.0. // Code from https://github.com/jquery/jquery/blob/e539bac79e666bba95bba86d690b4e609dca2286/src/selector/escapeSelector.js if ( !$.escapeSelector ) { // CSS string/identifier serialization // https://drafts.csswg.org/cssom/#common-serializing-idioms var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; var fcssescape = function( ch, asCodePoint ) { if ( asCodePoint ) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if ( ch === "\0" ) { return "\uFFFD"; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped return "\\" + ch; }; $.escapeSelector = function( sel ) { return ( sel + "" ).replace( rcssescape, fcssescape ); }; } // Support: jQuery 3.4.x or older // These methods have been defined in jQuery 3.5.0. if ( !$.fn.even || !$.fn.odd ) { $.fn.extend( { even: function() { return this.filter( function( i ) { return i % 2 === 0; } ); }, odd: function() { return this.filter( function( i ) { return i % 2 === 1; } ); } } ); } ; /*! * jQuery UI Keycode 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Keycode //>>group: Core //>>description: Provide keycodes as keynames //>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ var keycode = $.ui.keyCode = { BACKSPACE: 8, COMMA: 188, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, LEFT: 37, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SPACE: 32, TAB: 9, UP: 38 }; /*! * jQuery UI Labels 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: labels //>>group: Core //>>description: Find all the labels associated with a given input //>>docs: http://api.jqueryui.com/labels/ var labels = $.fn.labels = function() { var ancestor, selector, id, labels, ancestors; if ( !this.length ) { return this.pushStack( [] ); } // Check control.labels first if ( this[ 0 ].labels && this[ 0 ].labels.length ) { return this.pushStack( this[ 0 ].labels ); } // Support: IE <= 11, FF <= 37, Android <= 2.3 only // Above browsers do not support control.labels. Everything below is to support them // as well as document fragments. control.labels does not work on document fragments labels = this.eq( 0 ).parents( "label" ); // Look for the label based on the id id = this.attr( "id" ); if ( id ) { // We don't search against the document in case the element // is disconnected from the DOM ancestor = this.eq( 0 ).parents().last(); // Get a full set of top level ancestors ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); // Create a selector for the label based on the id selector = "label[for='" + $.escapeSelector( id ) + "']"; labels = labels.add( ancestors.find( selector ).addBack( selector ) ); } // Return whatever we have found for labels return this.pushStack( labels ); }; /*! * jQuery UI Scroll Parent 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: scrollParent //>>group: Core //>>description: Get the closest ancestor element that is scrollable. //>>docs: http://api.jqueryui.com/scrollParent/ var scrollParent = $.fn.scrollParent = function( includeHidden ) { var position = this.css( "position" ), excludeStaticParent = position === "absolute", overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, scrollParent = this.parents().filter( function() { var parent = $( this ); if ( excludeStaticParent && parent.css( "position" ) === "static" ) { return false; } return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); } ).eq( 0 ); return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; }; /*! * jQuery UI Tabbable 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: :tabbable Selector //>>group: Core //>>description: Selects elements which can be tabbed to. //>>docs: http://api.jqueryui.com/tabbable-selector/ var tabbable = $.extend( $.expr.pseudos, { tabbable: function( element ) { var tabIndex = $.attr( element, "tabindex" ), hasTabindex = tabIndex != null; return ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex ); } } ); /*! * jQuery UI Unique ID 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: uniqueId //>>group: Core //>>description: Functions to generate and remove uniqueId's //>>docs: http://api.jqueryui.com/uniqueId/ var uniqueId = $.fn.extend( { uniqueId: ( function() { var uuid = 0; return function() { return this.each( function() { if ( !this.id ) { this.id = "ui-id-" + ( ++uuid ); } } ); }; } )(), removeUniqueId: function() { return this.each( function() { if ( /^ui-id-\d+$/.test( this.id ) ) { $( this ).removeAttr( "id" ); } } ); } } ); /*! * jQuery UI Accordion 1.13.0 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Accordion //>>group: Widgets /* eslint-disable max-len */ //>>description: Displays collapsible content panels for presenting information in a limited amount of space. /* eslint-enable max-len */ //>>docs: http://api.jqueryui.com/accordion/ //>>demos: http://jqueryui.com/accordion/ //>>css.structure: ../../themes/base/core.css //>>css.structure: ../../themes/base/accordion.css //>>css.theme: ../../themes/base/theme.css var widgetsAccordion = $.widget( "ui.accordion", { version: "1.13.0", options: { active: 0, animate: {}, classes: { "ui-accordion-header": "ui-corner-top", "ui-accordion-header-collapsed": "ui-corner-all", "ui-accordion-content": "ui-corner-bottom" }, collapsible: false, event: "click", header: function( elem ) { return elem.find( "> li > :first-child" ).add( elem.find( "> :not(li)" ).even() ); }, heightStyle: "auto", icons: { activeHeader: "ui-icon-triangle-1-s", header: "ui-icon-triangle-1-e" }, // Callbacks activate: null, beforeActivate: null }, hideProps: { borderTopWidth: "hide", borderBottomWidth: "hide", paddingTop: "hide", paddingBottom: "hide", height: "hide" }, showProps: { borderTopWidth: "show", borderBottomWidth: "show", paddingTop: "show", paddingBottom: "show", height: "show" }, _create: function() { var options = this.options; this.prevShow = this.prevHide = $(); this._addClass( "ui-accordion", "ui-widget ui-helper-reset" ); this.element.attr( "role", "tablist" ); // Don't allow collapsible: false and active: false / null if ( !options.collapsible && ( options.active === false || options.active == null ) ) { options.active = 0; } this._processPanels(); // handle negative values if ( options.active < 0 ) { options.active += this.headers.length; } this._refresh(); }, _getCreateEventData: function() { return { header: this.active, panel: !this.active.length ? $() : this.active.next() }; }, _createIcons: function() { var icon, children, icons = this.options.icons; if ( icons ) { icon = $( "" ); this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header ); icon.prependTo( this.headers ); children = this.active.children( ".ui-accordion-header-icon" ); this._removeClass( children, icons.header ) ._addClass( children, null, icons.activeHeader ) ._addClass( this.headers, "ui-accordion-icons" ); } }, _destroyIcons: function() { this._removeClass( this.headers, "ui-accordion-icons" ); this.headers.children( ".ui-accordion-header-icon" ).remove(); }, _destroy: function() { var contents; // Clean up main element this.element.removeAttr( "role" ); // Clean up headers this.headers .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" ) .removeUniqueId(); this._destroyIcons(); // Clean up content panels contents = this.headers.next() .css( "display", "" ) .removeAttr( "role aria-hidden aria-labelledby" ) .removeUniqueId(); if ( this.options.heightStyle !== "content" ) { contents.css( "height", "" ); } }, _setOption: function( key, value ) { if ( key === "active" ) { // _activate() will handle invalid values and update this.options this._activate( value ); return; } if ( key === "event" ) { if ( this.options.event ) { this._off( this.headers, this.options.event ); } this._setupEvents( value ); } this._super( key, value ); // Setting collapsible: false while collapsed; open first panel if ( key === "collapsible" && !value && this.options.active === false ) { this._activate( 0 ); } if ( key === "icons" ) { this._destroyIcons(); if ( value ) { this._createIcons(); } } }, _setOptionDisabled: function( value ) { this._super( value ); this.element.attr( "aria-disabled", value ); // Support: IE8 Only // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE // so we need to add the disabled class to the headers and panels this._toggleClass( null, "ui-state-disabled", !!value ); this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled", !!value ); }, _keydown: function( event ) { if ( event.altKey || event.ctrlKey ) { return; } var keyCode = $.ui.keyCode, length = this.headers.length, currentIndex = this.headers.index( event.target ), toFocus = false; switch ( event.keyCode ) { case keyCode.RIGHT: case keyCode.DOWN: toFocus = this.headers[ ( currentIndex + 1 ) % length ]; break; case keyCode.LEFT: case keyCode.UP: toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; break; case keyCode.SPACE: case keyCode.ENTER: this._eventHandler( event ); break; case keyCode.HOME: toFocus = this.headers[ 0 ]; break; case keyCode.END: toFocus = this.headers[ length - 1 ]; break; } if ( toFocus ) { $( event.target ).attr( "tabIndex", -1 ); $( toFocus ).attr( "tabIndex", 0 ); $( toFocus ).trigger( "focus" ); event.preventDefault(); } }, _panelKeyDown: function( event ) { if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { $( event.currentTarget ).prev().trigger( "focus" ); } }, refresh: function() { var options = this.options; this._processPanels(); // Was collapsed or no panel if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { options.active = false; this.active = $(); // active false only when collapsible is true } else if ( options.active === false ) { this._activate( 0 ); // was active, but active panel is gone } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { // all remaining panel are disabled if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) { options.active = false; this.active = $(); // activate previous panel } else { this._activate( Math.max( 0, options.active - 1 ) ); } // was active, active panel still exists } else { // make sure active index is correct options.active = this.headers.index( this.active ); } this._destroyIcons(); this._refresh(); }, _processPanels: function() { var prevHeaders = this.headers, prevPanels = this.panels; if ( typeof this.options.header === "function" ) { this.headers = this.options.header( this.element ); } else { this.headers = this.element.find( this.options.header ); } this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed", "ui-state-default" ); this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide(); this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" ); // Avoid memory leaks (#10056) if ( prevPanels ) { this._off( prevHeaders.not( this.headers ) ); this._off( prevPanels.not( this.panels ) ); } }, _refresh: function() { var maxHeight, options = this.options, heightStyle = options.heightStyle, parent = this.element.parent(); this.active = this._findActive( options.active ); this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" ) ._removeClass( this.active, "ui-accordion-header-collapsed" ); this._addClass( this.active.next(), "ui-accordion-content-active" ); this.active.next().show(); this.headers .attr( "role", "tab" ) .each( function() { var header = $( this ), headerId = header.uniqueId().attr( "id" ), panel = header.next(), panelId = panel.uniqueId().attr( "id" ); header.attr( "aria-controls", panelId ); panel.attr( "aria-labelledby", headerId ); } ) .next() .attr( "role", "tabpanel" ); this.headers .not( this.active ) .attr( { "aria-selected": "false", "aria-expanded": "false", tabIndex: -1 } ) .next() .attr( { "aria-hidden": "true" } ) .hide(); // Make sure at least one header is in the tab order if ( !this.active.length ) { this.headers.eq( 0 ).attr( "tabIndex", 0 ); } else { this.active.attr( { "aria-selected": "true", "aria-expanded": "true", tabIndex: 0 } ) .next() .attr( { "aria-hidden": "false" } ); } this._createIcons(); this._setupEvents( options.event ); if ( heightStyle === "fill" ) { maxHeight = parent.height(); this.element.siblings( ":visible" ).each( function() { var elem = $( this ), position = elem.css( "position" ); if ( position === "absolute" || position === "fixed" ) { return; } maxHeight -= elem.outerHeight( true ); } ); this.headers.each( function() { maxHeight -= $( this ).outerHeight( true ); } ); this.headers.next() .each( function() { $( this ).height( Math.max( 0, maxHeight - $( this ).innerHeight() + $( this ).height() ) ); } ) .css( "overflow", "auto" ); } else if ( heightStyle === "auto" ) { maxHeight = 0; this.headers.next() .each( function() { var isVisible = $( this ).is( ":visible" ); if ( !isVisible ) { $( this ).show(); } maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); if ( !isVisible ) { $( this ).hide(); } } ) .height( maxHeight ); } }, _activate: function( index ) { var active = this._findActive( index )[ 0 ]; // Trying to activate the already active panel if ( active === this.active[ 0 ] ) { return; } // Trying to collapse, simulate a click on the currently active header active = active || this.active[ 0 ]; this._eventHandler( { target: active, currentTarget: active, preventDefault: $.noop } ); }, _findActive: function( selector ) { return typeof selector === "number" ? this.headers.eq( selector ) : $(); }, _setupEvents: function( event ) { var events = { keydown: "_keydown" }; if ( event ) { $.each( event.split( " " ), function( index, eventName ) { events[ eventName ] = "_eventHandler"; } ); } this._off( this.headers.add( this.headers.next() ) ); this._on( this.headers, events ); this._on( this.headers.next(), { keydown: "_panelKeyDown" } ); this._hoverable( this.headers ); this._focusable( this.headers ); }, _eventHandler: function( event ) { var activeChildren, clickedChildren, options = this.options, active = this.active, clicked = $( event.currentTarget ), clickedIsActive = clicked[ 0 ] === active[ 0 ], collapsing = clickedIsActive && options.collapsible, toShow = collapsing ? $() : clicked.next(), toHide = active.next(), eventData = { oldHeader: active, oldPanel: toHide, newHeader: collapsing ? $() : clicked, newPanel: toShow }; event.preventDefault(); if ( // click on active header, but not collapsible ( clickedIsActive && !options.collapsible ) || // allow canceling activation ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { return; } options.active = collapsing ? false : this.headers.index( clicked ); // When the call to ._toggle() comes after the class changes // it causes a very odd bug in IE 8 (see #6720) this.active = clickedIsActive ? $() : clicked; this._toggle( eventData ); // Switch classes // corner classes on the previously active header stay after the animation this._removeClass( active, "ui-accordion-header-active", "ui-state-active" ); if ( options.icons ) { activeChildren = active.children( ".ui-accordion-header-icon" ); this._removeClass( activeChildren, null, options.icons.activeHeader ) ._addClass( activeChildren, null, options.icons.header ); } if ( !clickedIsActive ) { this._removeClass( clicked, "ui-accordion-header-collapsed" ) ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" ); if ( options.icons ) { clickedChildren = clicked.children( ".ui-accordion-header-icon" ); this._removeClass( clickedChildren, null, options.icons.header ) ._addClass( clickedChildren, null, options.icons.activeHeader ); } this._addClass( clicked.next(), "ui-accordion-content-active" ); } }, _toggle: function( data ) { var toShow = data.newPanel, toHide = this.prevShow.length ? this.prevShow : data.oldPanel; // Handle activating a panel during the animation for another activation this.prevShow.add( this.prevHide ).stop( true, true ); this.prevShow = toShow; this.prevHide = toHide; if ( this.options.animate ) { this._animate( toShow, toHide, data ); } else { toHide.hide(); toShow.show(); this._toggleComplete( data ); } toHide.attr( { "aria-hidden": "true" } ); toHide.prev().attr( { "aria-selected": "false", "aria-expanded": "false" } ); // if we're switching panels, remove the old header from the tab order // if we're opening from collapsed state, remove the previous header from the tab order // if we're collapsing, then keep the collapsing header in the tab order if ( toShow.length && toHide.length ) { toHide.prev().attr( { "tabIndex": -1, "aria-expanded": "false" } ); } else if ( toShow.length ) { this.headers.filter( function() { return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0; } ) .attr( "tabIndex", -1 ); } toShow .attr( "aria-hidden", "false" ) .prev() .attr( { "aria-selected": "true", "aria-expanded": "true", tabIndex: 0 } ); }, _animate: function( toShow, toHide, data ) { var total, easing, duration, that = this, adjust = 0, boxSizing = toShow.css( "box-sizing" ), down = toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) ), animate = this.options.animate || {}, options = down && animate.down || animate, complete = function() { that._toggleComplete( data ); }; if ( typeof options === "number" ) { duration = options; } if ( typeof options === "string" ) { easing = options; } // fall back from options to animation in case of partial down settings easing = easing || options.easing || animate.easing; duration = duration || options.duration || animate.duration; if ( !toHide.length ) { return toShow.animate( this.showProps, duration, easing, complete ); } if ( !toShow.length ) { return toHide.animate( this.hideProps, duration, easing, complete ); } total = toShow.show().outerHeight(); toHide.animate( this.hideProps, { duration: duration, easing: easing, step: function( now, fx ) { fx.now = Math.round( now ); } } ); toShow .hide() .animate( this.showProps, { duration: duration, easing: easing, complete: complete, step: function( now, fx ) { fx.now = Math.round( now ); if ( fx.prop !== "height" ) { if ( boxSizing === "content-box" ) { adjust += fx.now; } } else if ( that.options.heightStyle !== "content" ) { fx.now = Math.round( total - toHide.outerHeight() - adjust ); adjust = 0; } } } ); }, _toggleComplete: function( data ) { var toHide = data.oldPanel, prev = toHide.prev(); this._removeClass( toHide, "ui-accordion-content-active" ); this._removeClass( prev, "ui-accordion-header-active" ) ._addClass( prev, "ui-accordion-header-collapsed" ); // Work around for rendering bug in IE (#5421) if ( toHide.length ) { toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; } this._trigger( "activate", null, data ); } } ); var safeActiveElement = $.ui.safeActiveElement = function( document ) { var activeElement; // Support: IE 9 only // IE9 throws an "Unspecified error" accessing document.activeElement from an ').appendTo($wrapper); /** * Use the YouTube API to create a new player * * @private */ var create = function () { if (!$placeholder.is(':visible') || player !== undefined) { return; } if (window.YT === undefined) { // Load API first loadAPI(create); return; } if (YT.Player === undefined) { return; } var width = $wrapper.width(); if (width < 200) { width = 200; } var loadCaptionsModule = true; var videoId = getId(sources[0].path); player = new YT.Player(id, { width: width, height: width * (9/16), videoId: videoId, playerVars: { origin: ORIGIN, // Hardcoded autoplay to false to avoid playing videos on init autoplay: 0, controls: options.controls ? 1 : 0, disablekb: options.controls ? 0 : 1, fs: 0, loop: options.loop ? 1 : 0, playlist: options.loop ? videoId : undefined, rel: 0, showinfo: 0, iv_load_policy: 3, wmode: "opaque", start: Math.floor(options.startAt), playsinline: 1 }, events: { onReady: function () { self.trigger('ready'); self.trigger('loaded'); if (!options.autoplay) { self.toPause = true; } if (options.deactivateSound) { self.mute(); } }, onApiChange: function () { if (loadCaptionsModule) { loadCaptionsModule = false; // Always load captions player.loadModule('captions'); } var trackList; try { // Grab tracklist from player trackList = player.getOption('captions', 'tracklist'); } catch (err) {} if (trackList && trackList.length) { // Format track list into valid track options var trackOptions = []; for (var i = 0; i < trackList.length; i++) { trackOptions.push(new H5P.Video.LabelValue(trackList[i].displayName, trackList[i].languageCode)); } // Captions are ready for loading self.trigger('captions', trackOptions); } }, onStateChange: function (state) { if (state.data > -1 && state.data < 4) { if (self.toPause) { // if video buffering, was likely paused already - skip if (state.data === H5P.Video.BUFFERING) { delete self.toPause; } else { self.pause(); } } // Fix for keeping playback rate in IE11 if (H5P.Video.IE11_PLAYBACK_RATE_FIX && state.data === H5P.Video.PLAYING && playbackRate !== 1) { // YT doesn't know that IE11 changed the rate so it must be reset before it's set to the correct value player.setPlaybackRate(1); player.setPlaybackRate(playbackRate); } // End IE11 fix self.trigger('stateChange', state.data); } }, onPlaybackQualityChange: function (quality) { self.trigger('qualityChange', quality.data); }, onPlaybackRateChange: function (playbackRate) { self.trigger('playbackRateChange', playbackRate.data); }, onError: function (error) { var message; switch (error.data) { case 2: message = l10n.invalidYtId; break; case 100: message = l10n.unknownYtId; break; case 101: case 150: message = l10n.restrictedYt; break; default: message = l10n.unknownError + ' ' + error.data; break; } self.trigger('error', message); } } }); player.getIframe().style = "position:absolute;top:0;left:0;width:100%;height:100%;"; }; /** * Indicates if the video must be clicked for it to start playing. * For instance YouTube videos on iPad must be pressed to start playing. * * @public */ if (navigator.userAgent.match(/iPad/i)) { self.pressToPlay = true; } else { try { if (document.featurePolicy.allowsFeature('autoplay') === false) { self.pressToPlay = true; } } catch (err) {} } /** * Appends the video player to the DOM. * * @public * @param {jQuery} $container */ self.appendTo = function ($container) { $container.addClass('h5p-youtube').append($wrapper); create(); }; /** * Get list of available qualities. Not available until after play. * * @public * @returns {Array} */ self.getQualities = function () { if (!player || !player.getAvailableQualityLevels) { return; } var qualities = player.getAvailableQualityLevels(); if (!qualities.length) { return; // No qualities } // Add labels for (var i = 0; i < qualities.length; i++) { var quality = qualities[i]; var label = (LABELS[quality] !== undefined ? LABELS[quality] : 'Unknown'); // TODO: l10n qualities[i] = { name: quality, label: LABELS[quality] }; } return qualities; }; /** * Get current playback quality. Not available until after play. * * @public * @returns {String} */ self.getQuality = function () { if (!player || !player.getPlaybackQuality) { return; } var quality = player.getPlaybackQuality(); return quality === 'unknown' ? undefined : quality; }; /** * Set current playback quality. Not available until after play. * Listen to event "qualityChange" to check if successful. * * @public * @params {String} [quality] */ self.setQuality = function (quality) { if (!player || !player.setPlaybackQuality) { return; } player.setPlaybackQuality(quality); }; /** * Start the video. * * @public */ self.play = function () { if (!player || !player.playVideo) { self.on('ready', self.play); return; } player.playVideo(); }; /** * Pause the video. * * @public */ self.pause = function () { delete self.toPause; self.off('ready', self.play); if (!player || !player.pauseVideo) { return; } player.pauseVideo(); }; /** * Seek video to given time. * * @public * @param {Number} time */ self.seek = function (time) { if (!player || !player.seekTo) { return; } player.seekTo(time, true); }; /** * Recreate player with initial time * * @public * @param {Number} time */ self.resetPlayback = function (time) { options.startAt = time; if (player) { if (player.getPlayerState() === H5P.Video.PLAYING) { player.pauseVideo(); self.trigger('stateChange', H5P.Video.PAUSED); } player.destroy(); player = undefined; } create(); } /** * Get elapsed time since video beginning. * * @public * @returns {Number} */ self.getCurrentTime = function () { if (!player || !player.getCurrentTime) { return; } return player.getCurrentTime(); }; /** * Get total video duration time. * * @public * @returns {Number} */ self.getDuration = function () { if (!player || !player.getDuration) { return; } return player.getDuration(); }; /** * Get percentage of video that is buffered. * * @public * @returns {Number} Between 0 and 100 */ self.getBuffered = function () { if (!player || !player.getVideoLoadedFraction) { return; } return player.getVideoLoadedFraction() * 100; }; /** * Turn off video sound. * * @public */ self.mute = function () { if (!player || !player.mute) { return; } player.mute(); }; /** * Turn on video sound. * * @public */ self.unMute = function () { if (!player || !player.unMute) { return; } player.unMute(); }; /** * Check if video sound is turned on or off. * * @public * @returns {Boolean} */ self.isMuted = function () { if (!player || !player.isMuted) { return; } return player.isMuted(); }; /** * Check if video is loaded and ready to play. * * @public * @returns {Boolean} */ self.isLoaded = function () { if (!player || !player.getPlayerState) { return; } return player.getPlayerState() === 5; }; /** * Return the video sound level. * * @public * @returns {Number} Between 0 and 100. */ self.getVolume = function () { if (!player || !player.getVolume) { return; } return player.getVolume(); }; /** * Set video sound level. * * @public * @param {Number} level Between 0 and 100. */ self.setVolume = function (level) { if (!player || !player.setVolume) { return; } player.setVolume(level); }; /** * Get list of available playback rates. * * @public * @returns {Array} available playback rates */ self.getPlaybackRates = function () { if (!player || !player.getAvailablePlaybackRates) { return; } var playbackRates = player.getAvailablePlaybackRates(); if (!playbackRates.length) { return; // No rates, but the array should contain at least 1 } return playbackRates; }; /** * Get current playback rate. * * @public * @returns {Number} such as 0.25, 0.5, 1, 1.25, 1.5 and 2 */ self.getPlaybackRate = function () { if (!player || !player.getPlaybackRate) { return; } return player.getPlaybackRate(); }; /** * Set current playback rate. * Listen to event "playbackRateChange" to check if successful. * * @public * @params {Number} suggested rate that may be rounded to supported values */ self.setPlaybackRate = function (newPlaybackRate) { if (!player || !player.setPlaybackRate) { return; } playbackRate = Number(newPlaybackRate); player.setPlaybackRate(playbackRate); }; /** * Set current captions track. * * @param {H5P.Video.LabelValue} Captions track to show during playback */ self.setCaptionsTrack = function (track) { player.setOption('captions', 'track', track ? {languageCode: track.value} : {}); }; /** * Figure out which captions track is currently used. * * @return {H5P.Video.LabelValue} Captions track */ self.getCaptionsTrack = function () { var track = player.getOption('captions', 'track'); return (track.languageCode ? new H5P.Video.LabelValue(track.displayName, track.languageCode) : null); }; // Respond to resize events by setting the YT player size. self.on('resize', function () { if (!$wrapper.is(':visible')) { return; } if (!player) { // Player isn't created yet. Try again. create(); return; } // Use as much space as possible $wrapper.css({ width: '100%', height: 'auto' }); var width = $wrapper[0].clientWidth; var height = options.fit ? $wrapper[0].clientHeight : (width * (9/16)); // Validate height before setting if (height > 0) { // Set size $wrapper.css({ width: width + 'px', height: height + 'px' }); player.setSize(width, height); } }); } /** * Check to see if we can play any of the given sources. * * @public * @static * @param {Array} sources * @returns {Boolean} */ YouTube.canPlay = function (sources) { return getId(sources[0].path); }; /** * Find id of YouTube video from given URL. * * @private * @param {String} url * @returns {String} YouTube video identifier */ var getId = function (url) { // Has some false positives, but should cover all regular URLs that people can find var matches = url.match(/(?:(?:youtube.com\/(?:attribution_link\?(?:\S+))?(?:v\/|embed\/|watch\/|(?:user\/(?:\S+)\/)?watch(?:\S+)v\=))|(?:youtu.be\/|y2u.be\/))([A-Za-z0-9_-]{11})/i); if (matches && matches[1]) { return matches[1]; } }; /** * Load the IFrame Player API asynchronously. */ var loadAPI = function (loaded) { if (window.onYouTubeIframeAPIReady !== undefined) { // Someone else is loading, hook in var original = window.onYouTubeIframeAPIReady; window.onYouTubeIframeAPIReady = function (id) { loaded(id); original(id); }; } else { // Load the API our self var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); window.onYouTubeIframeAPIReady = loaded; } }; /** @constant {Object} */ var LABELS = { highres: '2160p', // Old API support hd2160: '2160p', // (New API) hd1440: '1440p', hd1080: '1080p', hd720: '720p', large: '480p', medium: '360p', small: '240p', tiny: '144p', auto: 'Auto' }; /** @private */ var numInstances = 0; // Extract the current origin (used for security) var ORIGIN = window.location.href.match(/http[s]?:\/\/[^\/]+/); ORIGIN = !ORIGIN || ORIGIN[0] === undefined ? undefined : ORIGIN[0]; // ORIGIN = undefined is needed to support fetching file from device local storage return YouTube; })(H5P.jQuery); // Register video handler H5P.videoHandlers = H5P.videoHandlers || []; H5P.videoHandlers.push(H5P.VideoYouTube); ; /** @namespace H5P */ H5P.VideoPanopto = (function ($) { /** * Panopto video player for H5P. * * @class * @param {Array} sources Video files to use * @param {Object} options Settings for the player * @param {Object} l10n Localization strings */ function Panopto(sources, options, l10n) { var self = this; self.volume = 100; self.toSeek = undefined; var player; var playbackRate = 1; let canHasAutoplay; var id = 'h5p-panopto-' + numInstances; numInstances++; let isLoaded = false; let isPlayerReady = false; var $wrapper = $('
'); var $placeholder = $('
', { id: id, html: '
' + l10n.loading + '
' }).appendTo($wrapper); // Determine autoplay/play. try { if (document.featurePolicy.allowsFeature('autoplay') !== false) { canHasAutoplay = true; } } catch (err) {} /** * Use the Panopto API to create a new player * * @private */ var create = function () { if (!$placeholder.is(':visible') || player !== undefined) { return; } if (typeof EmbedApi === 'undefined') { // Load API first loadAPI(create); return; } var width = $wrapper.width(); if (width < 200) { width = 200; } const videoId = getId(sources[0].path); player = new EmbedApi(id, { width: width, height: width * (9/16), serverName: videoId[0], sessionId: videoId[1], videoParams: { // Optional interactivity: 'none', showtitle: false, autohide: true, offerviewer: false, autoplay: false, showbrand: false, start: 0, hideoverlay: !options.controls, }, events: { onIframeReady: function () { isPlayerReady = true; $placeholder.children(0).text(''); if (options.autoplay && canHasAutoplay) { player.loadVideo(); isLoaded = true; } self.trigger('containerLoaded'); self.trigger('resize'); // Avoid black iframe if loading is slow }, onReady: function () { self.videoLoaded = true; self.trigger('loaded'); if (typeof self.oldTime === 'number') { self.seek(self.oldTime); } else if (typeof self.startAt === 'number' && self.startAt > 0) { self.seek(self.startAt); } if (player.hasCaptions()) { const captions = []; const captionTracks = player.getCaptionTracks(); for (trackIndex in captionTracks) { captions.push(new H5P.Video.LabelValue(captionTracks[trackIndex], trackIndex)); } // Select active track currentTrack = player.getSelectedCaptionTrack(); currentTrack = captions[currentTrack] ? captions[currentTrack] : null; self.trigger('captions', captions); } }, onStateChange: function (state) { if ([H5P.Video.PLAYING, H5P.Video.PAUSED].includes(state) && typeof self.seekToTime === 'number') { player.seekTo(self.seekToTime); delete self.seekToTime; } // since panopto has different load sequence in IV, need additional condition here if (self.WAS_RESET) { self.WAS_RESET = false; } // TODO: Playback rate fix for IE11? if (state > -1 && state < 4) { self.trigger('stateChange', state); } }, onPlaybackRateChange: function () { self.trigger('playbackRateChange', self.getPlaybackRate()); }, onError: function (error) { if (error === ApiError.PlayWithSoundNotAllowed) { // pause and allow user to handle playing self.pause(); self.unMute(); // because player is automuted on this error } else { self.trigger('error', l10n.unknownError); } }, onLoginShown: function () { $placeholder.children().first().remove(); // Remove loading message self.trigger('loaded'); // Resize parent } } }); }; /** * Indicates if the video must be clicked for it to start playing. * This is always true for Panopto since all videos auto play. * * @public */ self.pressToPlay = true; /** * Appends the video player to the DOM. * * @public * @param {jQuery} $container */ self.appendTo = function ($container) { $container.addClass('h5p-panopto').append($wrapper); create(); }; /** * Get list of available qualities. Not available until after play. * * @public * @returns {Array} */ self.getQualities = function () { // Not available for Panopto }; /** * Get current playback quality. Not available until after play. * * @public * @returns {String} */ self.getQuality = function () { // Not available for Panopto }; /** * Set current playback quality. Not available until after play. * Listen to event "qualityChange" to check if successful. * * @public * @params {String} [quality] */ self.setQuality = function (quality) { // Not available for Panopto }; /** * Start the video. * * @public */ self.play = function () { if (!player || !player.playVideo || !isPlayerReady) { return; } if (isLoaded || self.videoLoaded) { player.playVideo(); } else { player.loadVideo(); // Loads and starts playing isLoaded = true; } }; /** * Pause the video. * * @public */ self.pause = function () { if (!player || !player.pauseVideo) { return; } try { player.pauseVideo(); } catch (err) { // Swallow Panopto throwing an error. This has been seen in the authoring // tool if Panopto has been used inside Iv inside CP } }; /** * Seek video to given time. * * @public * @param {Number} time */ self.seek = function (time) { if (!player || !player.seekTo || !self.videoLoaded) { return; } if (!player.isReady) { self.seekToTime = time; return; } player.seekTo(time); if (self.WAS_RESET) { // need to check just to be sure, since state === 1 is unusable delete self.seekToTime; self.WAS_RESET = false; } }; /** * Recreate player with initial time * * @public * @param {Number} time */ self.resetPlayback = function (time) { if (player && player.isReady && self.videoLoaded) { self.seek(time); self.pause(); } else { self.seekToTime = time; } } /** * Get elapsed time since video beginning. * * @public * @returns {Number} */ self.getCurrentTime = function () { if (!player || !player.getCurrentTime) { return; } return player.getCurrentTime(); }; /** * Get total video duration time. * * @public * @returns {Number} */ self.getDuration = function () { if (!player || !player.getDuration) { return; } return player.getDuration(); }; /** * Get percentage of video that is buffered. * * @public * @returns {Number} Between 0 and 100 */ self.getBuffered = function () { // Not available for Panopto }; /** * Turn off video sound. * * @public */ self.mute = function () { if (!player || !player.muteVideo) { return; } player.muteVideo(); }; /** * Turn on video sound. * * @public */ self.unMute = function () { if (!player || !player.unmuteVideo) { return; } player.unmuteVideo(); // The volume is set to 0 when the browser prevents autoplay, // causing there to be no sound despite unmuting self.setVolume(self.volume); }; /** * Check if video sound is turned on or off. * * @public * @returns {Boolean} */ self.isMuted = function () { if (!player || !player.isMuted) { return; } return player.isMuted(); }; /** * Check video is loaded and ready to play * * @public * @returns {Boolean} */ self.isLoaded = function () { return isPlayerReady; }; /** * Return the video sound level. * * @public * @returns {Number} Between 0 and 100. */ self.getVolume = function () { if (!player || !player.getVolume) { return; } return player.getVolume() * 100; }; /** * Set video sound level. * * @public * @param {Number} level Between 0 and 100. */ self.setVolume = function (level) { if (!player || !player.setVolume) { return; } player.setVolume(level/100); self.volume = level; }; /** * Get list of available playback rates. * * @public * @returns {Array} available playback rates */ self.getPlaybackRates = function () { return [0.25, 0.5, 1, 1.25, 1.5, 2]; }; /** * Get current playback rate. * * @public * @returns {Number} such as 0.25, 0.5, 1, 1.25, 1.5 and 2 */ self.getPlaybackRate = function () { if (!player || !player.getPlaybackRate) { return; } return player.getPlaybackRate(); }; /** * Set current playback rate. * Listen to event "playbackRateChange" to check if successful. * * @public * @params {Number} suggested rate that may be rounded to supported values */ self.setPlaybackRate = function (newPlaybackRate) { if (!player || !player.setPlaybackRate) { return; } player.setPlaybackRate(newPlaybackRate); }; /** * Set current captions track. * * @param {H5P.Video.LabelValue} Captions track to show during playback */ self.setCaptionsTrack = function (track) { if (!track) { player.disableCaptions(); currentTrack = null; } else { player.enableCaptions(track.value + ''); currentTrack = track; } }; /** * Figure out which captions track is currently used. * * @return {H5P.Video.LabelValue} Captions track */ self.getCaptionsTrack = function () { return currentTrack; // No function for getting active caption track? }; // Respond to resize events by setting the player size. self.on('resize', function () { if (!$wrapper.is(':visible')) { return; } if (!player) { // Player isn't created yet. Try again. create(); return; } $wrapper.removeAttr('font-size'); let width = $wrapper[0].clientWidth; let height = options.fit ? $wrapper[0].clientHeight : (width * (9/16)); const $iframe = $placeholder.children('iframe'); if ($iframe.length) { $iframe.attr('width', width); $iframe.attr('height', height); } $wrapper.css({ 'font-size': 0 }); }); let currentTrack; } /** * Check to see if we can play any of the given sources. * * @public * @static * @param {Array} sources * @returns {Boolean} */ Panopto.canPlay = function (sources) { return getId(sources[0].path); }; /** * Find id of YouTube video from given URL. * * @private * @param {String} url * @returns {String} Panopto video identifier */ var getId = function (url) { const matches = url.match(/^[^\/]+:\/\/([^\/]+)\/Panopto\/.+\?id=(.+)$/); if (matches && matches.length === 3) { return [matches[1], matches[2]]; } }; /** * Load the IFrame Player API asynchronously. */ var loadAPI = function (loaded) { if (window.onPanoptoEmbedApiReady !== undefined) { // Someone else is loading, hook in var original = window.onPanoptoEmbedApiReady; window.onPanoptoEmbedApiReady = function (id) { loaded(id); original(id); }; } else { // Load the API our self var tag = document.createElement('script'); tag.src = 'https://developers.panopto.com/scripts/v1.2.0/embedapi.min.js'; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); window.onPanoptoEmbedApiReady = loaded; } }; /** @private */ var numInstances = 0; return Panopto; })(H5P.jQuery); // Register video handler H5P.videoHandlers = H5P.videoHandlers || []; H5P.videoHandlers.push(H5P.VideoPanopto); ; /** @namespace Echo */ H5P.VideoEchoVideo = (() => { let numInstances = 0; const CONTROLS_HEIGHT = 100; /** * EchoVideo video player for H5P. * * @class * @param {Array} sources Video files to use * @param {Object} options Settings for the player * @param {Object} l10n Localization strings */ function EchoPlayer(sources, options, l10n) { // State variables for the Player. let player = undefined; let buffered = 0; let currentQuality; let trackOptions = []; let currentTime = 0; let duration = 0; let isMuted = false; let loadingComplete = false; let volume = 1; let playbackRate = 1; let qualities = []; let loadingFailedTimeout; let failedLoading = false; let ratio = 9 / 16; let currentState = H5P.Video.VIDEO_CUED; // Echo360 server doesn't sync seek time with regular play time fast enough let timelineUpdatesToSkip = 0; let timeUpdateTimeout; /* * Echo360 player does send time updates ~ 0.25 seconds by default and * ends playing the video without sending a final time update or an * Video Ended event. We take care of determining reaching the video end * ourselves. */ const echoMinUncertaintyCompensationS = 0.3; const timelineUpdateDeltaSlackMS = 50; let echoUncertaintyCompensationS = echoMinUncertaintyCompensationS; let previousTickMS; // Player specific immutable variables. const LOADING_TIMEOUT_IN_SECONDS = 30; const id = `h5p-echo-${++numInstances}`; const instanceId = H5P.createUUID(); const wrapperElement = document.createElement('div'); const placeholderElement = document.createElement('div'); placeholderElement.classList.add('h5p-video-loading'); placeholderElement.setAttribute('style', 'height: 100%; min-height: 200px; display: block; z-index: 100; border: none;'); placeholderElement.setAttribute('aria-label', l10n.loading); wrapperElement.setAttribute('id', id); wrapperElement.append(placeholderElement); /** * Remove all elements from the placeholder dom element. * * @private */ const removeLoadingIndicator = () => { placeholderElement.replaceChildren(); }; /** * Generate an array of objects for use in a dropdown from the list of resolutions. * @private * @param {Array} qualityLevels - list of objects with supported qualities for the media * @returns {Array} list of objects with label and name properties */ const mapQualityLevels = (qualityLevels) => { const qualities = qualityLevels.map((quality) => { return { label: quality.label.toLowerCase(), name: quality.value }; }); return qualities; }; /** * Register event listeners on the given Echo player. * * @private * @param {HTMLElement} player */ const registerEchoPlayerEventListeneners = (player) => { player.resolveLoading = null; player.loadingPromise = new Promise((resolve) => { player.resolveLoading = resolve; }); player.onload = async () => { clearTimeout(loadingFailedTimeout); player.loadingPromise.then(async () => { this.trigger('ready'); this.trigger('loaded'); this.loadingComplete = true; this.trigger('resize'); if (trackOptions.length) { this.trigger('captions', trackOptions); } const autoplayIsAllowed = !window.H5PEditor && await H5P.Video.isAutoplayAllowed(); if (options.autoplay && autoplayIsAllowed) { this.play(); } return true; }); }; window.addEventListener('message', (event) => { let message = ''; try { message = JSON.parse(event.data); } catch (e) { return; } if ( message.context !== 'Echo360' || message.instanceId !== instanceId ) { return; } if (message.event === 'init') { // Set ratio if width and height is received from Echo360 if (message.data.width && message.data.height) { // If controls are displayed we have to add a magic height to make it visible :( ratio = ((message.data.height + (options.controls ? CONTROLS_HEIGHT : 0)) / message.data.width); } duration = message.data.duration; this.setCurrentTime(message.data.currentTime ?? 0); textTracks = message.data.textTracks ?? []; if (message.data.captions) { trackOptions = textTracks.map((track) => new H5P.Video.LabelValue(track.label, track.value) ); } player.resolveLoading(); // Player sends `init` event after rebuffering, unfortunately. if (!this.wasInitialized) { qualities = mapQualityLevels(message.data.qualityOptions); currentQuality = qualities[0].name; this.trigger('qualityChange', currentQuality); } this.trigger('resize'); if (message.data.playing) { changeState(H5P.Video.PLAYING); } this.wasInitialized = true; } else if (message.event === 'timeline') { updateUncertaintyCompensation(); duration = message.data.duration ?? this.getDuration(); if (timelineUpdatesToSkip === 0) { this.setCurrentTime(message.data.currentTime ?? 0); } else { timelineUpdatesToSkip--; } /* * Should work, but it was better if the player itself clearly sent * the state (playing, paused, ended) instead of us having to infer. */ const compensatedTime = this.getCurrentTime() + echoUncertaintyCompensationS * this.getPlaybackRate() if ( currentState === H5P.Video.PLAYING && Math.ceil(compensatedTime) >= duration ) { changeState(H5P.Video.ENDED); if (options.loop) { this.seek(0); this.play(); } return; } if (message.data.playing) { timeUpdate(currentTime); changeState(H5P.Video.PLAYING); } else if (currentState === H5P.Video.PLAYING) { // Condition prevents video to be paused on startup changeState(H5P.Video.PAUSED); window.clearTimeout(timeUpdateTimeout); } } }); }; /** * Update the uncertainty compensation value. * Computes the delta time between the last two timeline events sent by the * Echo360 player and updates the compensation value. */ const updateUncertaintyCompensation = () => { if (currentState === H5P.Video.PLAYING) { const time = Date.now(); if (previousTickMS) { echoUncertaintyCompensationS = Math.max( echoMinUncertaintyCompensationS, (time - previousTickMS + timelineUpdateDeltaSlackMS) / 1000 ) } else { echoUncertaintyCompensationS = echoMinUncertaintyCompensationS; } previousTickMS = time; } else { previousTickMS = undefined; } } /** * Change state of the player. * @param {number} state State id (H5P.Video[statename]). */ const changeState = (state) => { if (state !== currentState) { currentState = state; this.trigger('stateChange', state); } }; /** * Determine if the element is visible by computing the styles. * * @private * @param {HTMLElement} node - the element to check. * @returns {Boolean} true if it is visible. */ const isNodeVisible = (node) => { let style = window.getComputedStyle(node); if (node.offsetWidth === 0) { return false; } return ((style.display !== 'none') && (style.visibility !== 'hidden')); }; const timeUpdate = (time) => { window.clearTimeout(timeUpdateTimeout); this.lastTimeUpdate = Date.now(); timeUpdateTimeout = window.setTimeout(() => { if (currentState !== H5P.Video.PLAYING) { return; } const delta = (Date.now() - this.lastTimeUpdate) * this.getPlaybackRate(); this.setCurrentTime(currentTime + delta / 1000); timeUpdate(currentTime); }, 40); // 25 fps } /** * Create a new player by embedding an iframe. * * @private * @returns {Promise} */ const createEchoPlayer = async () => { if (!isNodeVisible(placeholderElement) || player !== undefined) { return; } // Since the SDK is loaded asynchronously below, explicitly set player to // null (unlike undefined) which indicates that creation has begun. This // allows the guard statement above to be hit if this function is called // more than once. player = null; let queryString = '?'; queryString += `instanceId=${instanceId}&`; if (options.controls) { queryString += 'controls=true&'; } if (options.disableFullscreen) { queryString += 'disableFullscreen=true&'; } if (options.deactivateSound) { queryString += 'deactivateSound=true&'; } if (options.startAt) { queryString += `startTimeMillis=${Math.round(options.startAt * 1000)}&`; } let source = ''; try { const url = new URL(sources[0].path + queryString); if (url.protocol !== 'https:' && url.protocol !== 'http:') { throw new Error('Invalid protocol'); } source = url.toString(); } catch (err) { } wrapperElement.innerHTML = ``; player = wrapperElement.firstChild; // Create a new player registerEchoPlayerEventListeneners(player); loadingFailedTimeout = setTimeout(() => { failedLoading = true; removeLoadingIndicator(); wrapperElement.innerHTML = `

${l10n.unknownError}

`; wrapperElement.style.cssText = 'width: null; height: null;'; this.trigger('resize'); this.trigger('error', l10n.unknownError); }, LOADING_TIMEOUT_IN_SECONDS * 1000); }; /** * Appends the video player to the DOM. * * @public * @param {jQuery} $container */ this.appendTo = ($container) => { $container.addClass('h5p-echo').append(wrapperElement); createEchoPlayer(); }; /** * Determine if the video has loaded. * * @public * @returns {Boolean} */ this.isLoaded = () => { return loadingComplete; }; /** * Get list of available qualities. * * @public * @returns {Array} */ this.getQualities = () => { return qualities; }; /** * Get the current quality. * * @public * @returns {String} Current quality identifier */ this.getQuality = () => { return currentQuality; }; /** * Set the playback quality. * * @public * @param {String} quality */ this.setQuality = async (quality) => { this.post('quality', quality); currentQuality = quality; this.trigger('qualityChange', currentQuality); }; /** * Start the video. * * @public */ this.play = () => { if (!player) { this.on('ready', this.play); return; } this.post('play', 0); }; /** * Pause the video. * * @public */ this.pause = () => { // Compensate for Echo360's delayed time updates timelineUpdatesToSkip = 1; this.post('pause', 0); }; /** * Seek video to given time. * * @public * @param {Number} time */ this.seek = (time) => { this.post('seek', time); this.setCurrentTime(time); // Compensate for Echo360's delayed time updates timelineUpdatesToSkip = 1; }; /** * Post a window message to the iframe. * * @public * @param event * @param data */ this.post = (event, data) => { player?.contentWindow?.postMessage( JSON.stringify({ event: event, context: 'Echo360', instanceId: instanceId, data: data }), '*' ); }; /** * Return the current play position. * * @public * @returns {Number} Seconds elapsed since beginning of video */ this.getCurrentTime = () => { return currentTime; }; /** * Set current time. * @param {number} timeS Time in seconds. */ this.setCurrentTime = (timeS) => { currentTime = timeS; } /** * Return the video duration. * * @public * @returns {?Number} Video duration in seconds */ this.getDuration = () => { if (duration > 0) { return duration; } return null; }; /** * Get percentage of video that is buffered. * * @public * @returns {Number} Between 0 and 100 */ this.getBuffered = () => { return buffered; }; /** * Mute the video. * * @public */ this.mute = () => { this.post('mute', 0); isMuted = true; }; /** * Unmute the video. * * @public */ this.unMute = () => { this.post('unmute', 0); isMuted = false; }; /** * Whether the video is muted. * * @public * @returns {Boolean} True if the video is muted, false otherwise */ this.isMuted = () => { return isMuted; }; /** * Get the video player's current sound volume. * * @public * @returns {Number} Between 0 and 100. */ this.getVolume = () => { return volume; }; /** * Set the video player's sound volume. * * @public * @param {Number} level */ this.setVolume = (level) => { this.post('volume', level); volume = level; }; /** * Get list of available playback rates. * * @public * @returns {Array} Available playback rates */ this.getPlaybackRates = () => { return [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; }; /** * Get the current playback rate. * * @public * @returns {Number} e.g. 0.5, 1, 1.5 or 2 */ this.getPlaybackRate = () => { return playbackRate; }; /** * Set the current playback rate. * * @public * @param {Number} rate Must be one of available rates from getPlaybackRates */ this.setPlaybackRate = async (rate) => { const echoRate = parseFloat(rate); this.post('playbackrate', echoRate); playbackRate = rate; this.trigger('playbackRateChange', rate); }; /** * Set current captions track. * * @public * @param {H5P.Video.LabelValue} track Captions to display */ this.setCaptionsTrack = (track) => { const echoCaption = trackOptions.find( (trackItem) => track?.value === trackItem.value ); trackOptions.forEach(trackItem => { trackItem.mode = (trackItem === echoCaption) ? 'showing' : 'disabled'; }); this.post('captions', echoCaption ? echoCaption.value : 'off'); }; /** * Get current captions track. * * @public * @returns {H5P.Video.LabelValue|null} Current captions track. */ this.getCaptionsTrack = () => { return trackOptions.find( (trackItem) => trackItem.mode === 'showing' ) ?? null; }; this.on('resize', () => { if (failedLoading || !isNodeVisible(wrapperElement)) { return; } if (player === undefined) { // Player isn't created yet. Try again. createEchoPlayer(); return; } // Use as much space as possible wrapperElement.style.cssText = 'width: 100%; height: 100%;'; const width = wrapperElement.clientWidth; const height = options.fit ? wrapperElement.clientHeight : (width * (ratio)); // Validate height before setting if (height > 0) { // Set size wrapperElement.style.cssText = 'width: ' + width + 'px; height: ' + height + 'px;'; } }); } /** * Find id of video from given URL. * * @private * @param {String} url * @returns {String} Echo video identifier */ const getId = (url) => { const matches = url.match(/^[^/]+:\/\/(echo360[^/]+)\/media\/([^/]+)\/h5p.*$/i); if (matches && matches.length === 3) { return [matches[2], matches[2]]; } }; /** * Check to see if we can play any of the given sources. * * @public * @static * @param {Array} sources * @returns {Boolean} */ EchoPlayer.canPlay = (sources) => { return getId(sources[0].path); }; return EchoPlayer; })(H5P.jQuery); // Register video handler H5P.videoHandlers = H5P.videoHandlers || []; H5P.videoHandlers.push(H5P.VideoEchoVideo); ; /** @namespace H5P */ H5P.VideoHtml5 = (function ($) { /** * HTML5 video player for H5P. * * @class * @param {Array} sources Video files to use * @param {Object} options Settings for the player * @param {Object} l10n Localization strings */ function Html5(sources, options, l10n) { var self = this; /** * Small helper to ensure all video sources get the same cache buster. * * @private * @param {Object} source * @return {string} */ const getCrossOriginPath = function (source) { let path = H5P.getPath(source.path, self.contentId); if (video.crossOrigin !== null && H5P.addQueryParameter && H5PIntegration.crossoriginCacheBuster) { path = H5P.addQueryParameter(path, H5PIntegration.crossoriginCacheBuster); } return path }; /** * Register track to video * * @param {Object} trackData Track object * @param {string} trackData.kind Kind of track * @param {Object} trackData.track Source path * @param {string} [trackData.label] Label of track * @param {string} [trackData.srcLang] Language code */ const addTrack = function (trackData) { // Skip invalid tracks if (!trackData.kind || !trackData.track.path) { return; } var track = document.createElement('track'); track.kind = trackData.kind; track.src = getCrossOriginPath(trackData.track); // Uses same crossOrigin as parent. You cannot mix. if (trackData.label) { track.label = trackData.label; } if (trackData.srcLang) { track.srcLang = trackData.srcLang; } return track; }; /** * Small helper to set the inital video source. * Useful if some of the loading happens asynchronously. * NOTE: Setting the crossOrigin must happen before any of the * sources(poster, tracks etc.) are loaded * * @private */ const setInitialSource = function () { if (qualities[currentQuality] === undefined) { return; } if (H5P.setSource !== undefined) { H5P.setSource(video, qualities[currentQuality].source, self.contentId) } else { // Backwards compatibility (H5P < v1.22) const srcPath = H5P.getPath(qualities[currentQuality].source.path, self.contentId); if (H5P.getCrossOrigin !== undefined) { var crossOrigin = H5P.getCrossOrigin(srcPath); video.setAttribute('crossorigin', crossOrigin !== null ? crossOrigin : 'anonymous'); } video.src = srcPath; } // Add poster if provided if (options.poster) { video.poster = getCrossOriginPath(options.poster); // Uses same crossOrigin as parent. You cannot mix. } // Register tracks options.tracks.forEach(function (track, i) { var trackElement = addTrack(track); if (i === 0) { trackElement.default = true; } if (trackElement) { video.appendChild(trackElement); } }); }; /** * Displayed when the video is buffering * @private */ var $throbber = $('
', { 'class': 'h5p-video-loading' }); /** * Used to display error messages * @private */ var $error = $('
', { 'class': 'h5p-video-error' }); /** * Keep track of current state when changing quality. * @private */ var stateBeforeChangingQuality; var currentTimeBeforeChangingQuality; /** * Avoids firing the same event twice. * @private */ var lastState; /** * Keeps track whether or not the video has been loaded. * @private */ var isLoaded = false; /** * * @private */ var playbackRate = 1; var skipRateChange = false; // Create player var video = document.createElement('video'); // Sort sources into qualities var qualities = getQualities(sources, video); var currentQuality; numQualities = 0; for (let quality in qualities) { numQualities++; } if (numQualities > 1 && H5P.VideoHtml5.getExternalQuality !== undefined) { H5P.VideoHtml5.getExternalQuality(sources, function (chosenQuality) { if (qualities[chosenQuality] !== undefined) { currentQuality = chosenQuality; } setInitialSource(); }); } else { // Select quality and source currentQuality = getPreferredQuality(); if (currentQuality === undefined || qualities[currentQuality] === undefined) { // No preferred quality, pick the first. for (currentQuality in qualities) { if (qualities.hasOwnProperty(currentQuality)) { break; } } } setInitialSource(); } // Setting webkit-playsinline, which makes iOS 10 beeing able to play video // inside browser. video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); video.setAttribute('preload', 'metadata'); // Remove buttons in Chrome's video player: let controlsList = 'nodownload'; if (options.disableFullscreen) { controlsList += ' nofullscreen'; } if (options.disableRemotePlayback) { controlsList += ' noremoteplayback'; } video.setAttribute('controlsList', controlsList); // Remove picture in picture as it interfers with other video players video.disablePictureInPicture = true; // Set options video.disableRemotePlayback = (options.disableRemotePlayback ? true : false); video.controls = (options.controls ? true : false); // Hardcoded autoplay to false to avoid playing videos on init video.autoplay = false; video.loop = (options.loop ? true : false); video.className = 'h5p-video'; video.style.display = 'block'; if (options.fit) { // Style is used since attributes with relative sizes aren't supported by IE9. video.style.width = '100%'; video.style.height = '100%'; } /** * Helps registering events. * * @private * @param {String} native Event name * @param {String} h5p Event name * @param {String} [arg] Optional argument */ var mapEvent = function (native, h5p, arg) { video.addEventListener(native, function () { switch (h5p) { case 'loaded': isLoaded = true; if (stateBeforeChangingQuality !== undefined) { return; // Avoid loaded event when changing quality. } // Remove any errors if ($error.is(':visible')) { $error.remove(); } if (OLD_ANDROID_FIX) { var andLoaded = function () { video.removeEventListener('durationchange', andLoaded, false); // On Android seeking isn't ready until after play. self.trigger(h5p); }; video.addEventListener('durationchange', andLoaded, false); return; } if (video.poster) { $(video).one('play', function () { self.seek(self.getCurrentTime() || options.startAt); }); } else { self.seek(options.startAt); } break; case 'error': // Handle error and get message. arg = error(arguments[0], arguments[1]); break; case 'playbackRateChange': // Fix for keeping playback rate in IE11 if (skipRateChange) { skipRateChange = false; return; // Avoid firing event when changing back } if (H5P.Video.IE11_PLAYBACK_RATE_FIX && playbackRate != video.playbackRate) { // Intentional // Prevent change in playback rate not triggered by the user video.playbackRate = playbackRate; skipRateChange = true; return; } // End IE11 fix arg = self.getPlaybackRate(); break; } self.trigger(h5p, arg); }, false); }; /** * Handle errors from the video player. * * @private * @param {Object} code Error * @param {String} [message] * @returns {String} Human readable error message. */ var error = function (code, message) { if (code instanceof Event) { // No error code if (!code.target.error) { return ''; } switch (code.target.error.code) { case MediaError.MEDIA_ERR_ABORTED: message = l10n.aborted; break; case MediaError.MEDIA_ERR_NETWORK: message = l10n.networkFailure; break; case MediaError.MEDIA_ERR_DECODE: message = l10n.cannotDecode; break; case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED: message = l10n.formatNotSupported; break; case MediaError.MEDIA_ERR_ENCRYPTED: message = l10n.mediaEncrypted; break; } } if (!message) { message = l10n.unknownError; } // Hide throbber $throbber.remove(); // Display error message to user $error.text(message).insertAfter(video); // Pass message to our error event return message; }; /** * Appends the video player to the DOM. * * @public * @param {jQuery} $container */ self.appendTo = function ($container) { $container.append(video); }; /** * Get list of available qualities. Not available until after play. * * @public * @returns {Array} */ self.getQualities = function () { // Create reverse list var options = []; for (var q in qualities) { if (qualities.hasOwnProperty(q)) { options.splice(0, 0, { name: q, label: qualities[q].label }); } } if (options.length < 2) { // Do not return if only one quality. return; } return options; }; /** * Get current playback quality. Not available until after play. * * @public * @returns {String} */ self.getQuality = function () { return currentQuality; }; /** * Set current playback quality. Not available until after play. * Listen to event "qualityChange" to check if successful. * * @public * @params {String} [quality] */ self.setQuality = function (quality) { if (qualities[quality] === undefined || quality === currentQuality) { return; // Invalid quality } // Keep track of last choice setPreferredQuality(quality); // Avoid multiple loaded events if changing quality multiple times. if (!stateBeforeChangingQuality) { // Keep track of last state stateBeforeChangingQuality = lastState; // Keep track of current time currentTimeBeforeChangingQuality = video.currentTime; // Seek and start video again after loading. var loaded = function () { video.removeEventListener('loadedmetadata', loaded, false); if (OLD_ANDROID_FIX) { var andLoaded = function () { video.removeEventListener('durationchange', andLoaded, false); // On Android seeking isn't ready until after play. self.seek(currentTimeBeforeChangingQuality); }; video.addEventListener('durationchange', andLoaded, false); } else { // Seek to current time. self.seek(currentTimeBeforeChangingQuality); } // Always play to get image. video.play(); if (stateBeforeChangingQuality !== H5P.Video.PLAYING) { // Do not resume playing video.pause(); } // Done changing quality stateBeforeChangingQuality = undefined; // Remove any errors if ($error.is(':visible')) { $error.remove(); } }; video.addEventListener('loadedmetadata', loaded, false); } // Keep track of current quality currentQuality = quality; self.trigger('qualityChange', currentQuality); // Display throbber self.trigger('stateChange', H5P.Video.BUFFERING); // Change source video.src = getCrossOriginPath(qualities[quality].source); // (iPad does not support #t=). // Note: Optional tracks use same crossOrigin as the original. You cannot mix. // Remove poster so it will not show during quality change video.removeAttribute('poster'); }; /** * Starts the video. * * @public * @return {Promise|undefined} May return a Promise that resolves when * play has been processed. */ self.play = function () { if ($error.is(':visible')) { return; } if (!isLoaded) { // Make sure video is loaded before playing video.load(); video.addEventListener('loadeddata', function() { video.play(); }, false); } else { return video.play(); } }; /** * Pauses the video. * * @public */ self.pause = function () { video.pause(); }; /** * Seek video to given time. * * @public * @param {Number} time */ self.seek = function (time) { video.currentTime = time; // Use canplaythrough for IOs devices // Use loadedmetadata for all other devices. const eventName = navigator.userAgent.match(/iPad|iPod|iPhone/i) ? "canplaythrough" : "loadedmetadata"; function seekTo() { video.currentTime = time; video.removeEventListener(eventName, seekTo); }; if (video.readyState === 4) { seekTo(); } else { video.addEventListener(eventName, seekTo); } }; /** * Get elapsed time since video beginning. * * @public * @returns {Number} */ self.getCurrentTime = function () { return video.currentTime; }; /** * Get total video duration time. * * @public * @returns {Number} */ self.getDuration = function () { if (isNaN(video.duration)) { return; } return video.duration; }; /** * Get percentage of video that is buffered. * * @public * @returns {Number} Between 0 and 100 */ self.getBuffered = function () { // Find buffer currently playing from var buffered = 0; for (var i = 0; i < video.buffered.length; i++) { var from = video.buffered.start(i); var to = video.buffered.end(i); if (video.currentTime > from && video.currentTime < to) { buffered = to; break; } } // To percentage return buffered ? (buffered / video.duration) * 100 : 0; }; /** * Turn off video sound. * * @public */ self.mute = function () { video.muted = true; }; /** * Turn on video sound. * * @public */ self.unMute = function () { video.muted = false; }; /** * Check if video sound is turned on or off. * * @public * @returns {Boolean} */ self.isMuted = function () { return video.muted; }; /** * Returns the video sound level. * * @public * @returns {Number} Between 0 and 100. */ self.getVolume = function () { return video.volume * 100; }; /** * Set video sound level. * * @public * @param {Number} level Between 0 and 100. */ self.setVolume = function (level) { video.volume = level / 100; }; /** * Get list of available playback rates. * * @public * @returns {Array} available playback rates */ self.getPlaybackRates = function () { /* * not sure if there's a common rule about determining good speeds * using Google's standard options via a constant for setting */ var playbackRates = PLAYBACK_RATES; return playbackRates; }; /** * Get current playback rate. * * @public * @returns {Number} such as 0.25, 0.5, 1, 1.25, 1.5 and 2 */ self.getPlaybackRate = function () { return video.playbackRate; }; /** * Set current playback rate. * Listen to event "playbackRateChange" to check if successful. * * @public * @params {Number} suggested rate that may be rounded to supported values */ self.setPlaybackRate = function (newPlaybackRate) { playbackRate = newPlaybackRate; video.playbackRate = newPlaybackRate; }; /** * Set current captions track. * * @param {H5P.Video.LabelValue} Captions track to show during playback */ self.setCaptionsTrack = function (track) { for (var i = 0; i < video.textTracks.length; i++) { video.textTracks[i].mode = (track && track.value === i ? 'showing' : 'disabled'); } }; /** * Figure out which captions track is currently used. * * @return {H5P.Video.LabelValue} Captions track */ self.getCaptionsTrack = function () { for (var i = 0; i < video.textTracks.length; i++) { if (video.textTracks[i].mode === 'showing') { return new H5P.Video.LabelValue(video.textTracks[i].label, i); } } return null; }; // Register event listeners mapEvent('ended', 'stateChange', H5P.Video.ENDED); mapEvent('playing', 'stateChange', H5P.Video.PLAYING); mapEvent('pause', 'stateChange', H5P.Video.PAUSED); mapEvent('waiting', 'stateChange', H5P.Video.BUFFERING); mapEvent('loadedmetadata', 'loaded'); mapEvent('canplay', 'canplay'); mapEvent('error', 'error'); mapEvent('ratechange', 'playbackRateChange'); if (!video.controls) { // Disable context menu(right click) to prevent controls. video.addEventListener('contextmenu', function (event) { event.preventDefault(); }, false); } // Display throbber when buffering/loading video. self.on('stateChange', function (event) { var state = event.data; lastState = state; if (state === H5P.Video.BUFFERING) { $throbber.insertAfter(video); } else { $throbber.remove(); } }); // Load captions after the video is loaded self.on('loaded', function () { nextTick(function () { var textTracks = []; for (var i = 0; i < video.textTracks.length; i++) { textTracks.push(new H5P.Video.LabelValue(video.textTracks[i].label, i)); } if (textTracks.length) { self.trigger('captions', textTracks); } }); }); // Alternative to 'canplay' event /*self.on('resize', function () { if (video.offsetParent === null) { return; } video.style.width = '100%'; video.style.height = '100%'; var width = video.clientWidth; var height = options.fit ? video.clientHeight : (width * (video.videoHeight / video.videoWidth)); video.style.width = width + 'px'; video.style.height = height + 'px'; });*/ // Video controls are ready nextTick(function () { self.trigger('ready'); }); /** * Check video is loaded and ready play. * * @public * @param {Boolean} */ self.isLoaded = function () { return isLoaded; }; } /** * Check to see if we can play any of the given sources. * * @public * @static * @param {Array} sources * @returns {Boolean} */ Html5.canPlay = function (sources) { var video = document.createElement('video'); if (video.canPlayType === undefined) { return false; // Not supported } // Cycle through sources for (var i = 0; i < sources.length; i++) { var type = getType(sources[i]); if (type && video.canPlayType(type) !== '') { // We should be able to play this return true; } } return false; }; /** * Find source type. * * @private * @param {Object} source * @returns {String} */ var getType = function (source) { var type = source.mime; if (!type) { // Try to get type from URL var matches = source.path.match(/\.(\w+)$/); if (matches && matches[1]) { type = 'video/' + matches[1]; } } if (type && source.codecs) { // Add codecs type += '; codecs="' + source.codecs + '"'; } return type; }; /** * Sort sources into qualities. * * @private * @static * @param {Array} sources * @param {Object} video * @returns {Object} Quality mapping */ var getQualities = function (sources, video) { var qualities = {}; var qualityIndex = 1; var lastQuality; // Cycle through sources for (var i = 0; i < sources.length; i++) { var source = sources[i]; // Find and update type. var type = source.type = getType(source); // Check if we support this type var isPlayable = type && (type === 'video/unknown' || video.canPlayType(type) !== ''); if (!isPlayable) { continue; // We cannot play this source } if (source.quality === undefined) { /** * No quality metadata. Create a quality tag to separate multiple sources of the same type, * e.g. if two mp4 files with different quality has been uploaded */ if (lastQuality === undefined || qualities[lastQuality].source.type === type) { // Create a new quality tag source.quality = { name: 'q' + qualityIndex, label: (source.metadata && source.metadata.qualityName) ? source.metadata.qualityName : 'Quality ' + qualityIndex // TODO: l10n }; qualityIndex++; } else { /** * Assumes quality already exists in a different format. * Uses existing label for this quality. */ source.quality = qualities[lastQuality].source.quality; } } // Log last quality lastQuality = source.quality.name; // Look to see if quality exists var quality = qualities[lastQuality]; if (quality) { // We have a source with this quality. Check if we have a better format. if (source.mime.split('/')[1] === PREFERRED_FORMAT) { quality.source = source; } } else { // Add new source with quality. qualities[source.quality.name] = { label: source.quality.label, source: source }; } } return qualities; }; /** * Set preferred video quality. * * @private * @static * @param {String} quality Index of preferred quality */ var setPreferredQuality = function (quality) { try { localStorage.setItem('h5pVideoQuality', quality); } catch (err) { console.warn('Unable to set preferred video quality, localStorage is not available.'); } }; /** * Get preferred video quality. * * @private * @static * @returns {String} Index of preferred quality */ var getPreferredQuality = function () { // First check localStorage let quality; try { quality = localStorage.getItem('h5pVideoQuality'); } catch (err) { console.warn('Unable to retrieve preferred video quality from localStorage.'); } if (!quality) { try { // The fallback to old cookie solution var settings = document.cookie.split(';'); for (var i = 0; i < settings.length; i++) { var setting = settings[i].split('='); if (setting[0] === 'H5PVideoQuality') { quality = setting[1]; break; } } } catch (err) { console.warn('Unable to retrieve preferred video quality from cookie.'); } } return quality; }; /** * Helps schedule a task for the next tick. * @param {function} task */ var nextTick = function (task) { setTimeout(task, 0); }; /** @constant {Boolean} */ var OLD_ANDROID_FIX = false; /** @constant {Boolean} */ var PREFERRED_FORMAT = 'mp4'; /** @constant {Object} */ var PLAYBACK_RATES = [0.25, 0.5, 1, 1.25, 1.5, 2]; if (navigator.userAgent.indexOf('Android') !== -1) { // We have Android, check version. var version = navigator.userAgent.match(/AppleWebKit\/(\d+\.?\d*)/); if (version && version[1] && Number(version[1]) <= 534.30) { // Include fix for devices running the native Android browser. // (We don't know when video was fixed, so the number is just the lastest // native android browser we found.) OLD_ANDROID_FIX = true; } } else { if (navigator.userAgent.indexOf('Chrome') !== -1) { // If we're using chrome on a device that isn't Android, prefer the webm // format. This is because Chrome has trouble with some mp4 codecs. PREFERRED_FORMAT = 'webm'; } } return Html5; })(H5P.jQuery); // Register video handler H5P.videoHandlers = H5P.videoHandlers || []; H5P.videoHandlers.push(H5P.VideoHtml5); ; /** @namespace H5P */ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * The ultimate H5P video player! * * @class * @param {Object} parameters Options for this library. * @param {Object} parameters.visuals Visual options * @param {Object} parameters.playback Playback options * @param {Object} parameters.a11y Accessibility options * @param {Boolean} [parameters.startAt] Start time of video * @param {Number} id Content identifier * @param {Object} [extras] Extra parameters. */ function Video(parameters, id, extras = {}) { var self = this; self.oldTime = extras.previousState?.time; self.contentId = id; self.WAS_RESET = false; self.startAt = parameters.startAt || 0; self.hasNoAutoPause = parameters.playback?.hasNoAutoPause || false; // Ref youtube.js - ipad & youtube - issue self.pressToPlay = false; // Reference to the handler var handlerName = ''; // Initialize event inheritance H5P.EventDispatcher.call(self); // Default language localization parameters = $.extend(true, parameters, { l10n: { name: 'Video', loading: 'Video player loading...', noPlayers: 'Found no video players that supports the given video format.', noSources: 'Video source is missing.', aborted: 'Media playback has been aborted.', networkFailure: 'Network failure.', cannotDecode: 'Unable to decode media.', formatNotSupported: 'Video format not supported.', mediaEncrypted: 'Media encrypted.', unknownError: 'Unknown error.', vimeoPasswordError: 'Password-protected Vimeo videos are not supported.', vimeoPrivacyError: 'The Vimeo video cannot be used due to its privacy settings.', vimeoLoadingError: 'The Vimeo video could not be loaded.', invalidYtId: 'Invalid YouTube ID.', unknownYtId: 'Unable to find video with the given YouTube ID.', restrictedYt: 'The owner of this video does not allow it to be embedded.' } }); parameters.a11y = parameters.a11y || []; parameters.playback = parameters.playback || {}; parameters.visuals = $.extend( true, { disableFullscreen: false }, parameters.visuals ); /** @private */ var sources = []; if (parameters.sources) { for (var i = 0; i < parameters.sources.length; i++) { // Clone to avoid changing of parameters. var source = $.extend(true, {}, parameters.sources[i]); // Create working URL without html entities. source.path = $cleaner.html(source.path).text(); sources.push(source); } } /** @private */ var tracks = []; parameters.a11y.forEach(function (track) { // Clone to avoid changing of parameters. var clone = $.extend(true, {}, track); // Create working URL without html entities if (clone.track && clone.track.path) { clone.track.path = $cleaner.html(clone.track.path).text(); tracks.push(clone); } }); /** * Handle autoplay. If autoplay is disabled, it will still autopause when * video is not visible. * * @param {*} $container */ const handleAutoPlayPause = function ($container) { // Keep the current state let state; self.on('stateChange', function(event) { state = event.data; }); // Keep record of autopauses. // I.e: we don't wanna autoplay if the user has excplicitly paused. self.autoPaused = !self.pressToPlay; new IntersectionObserver(function (entries) { const entry = entries[0]; // This video element became visible if (entry.isIntersecting) { // Autoplay if autoplay is enabled and it was not explicitly // paused by a user if (parameters.playback.autoplay && self.autoPaused) { self.autoPaused = false; self.play(); } } else if (state !== Video.PAUSED && state !== Video.ENDED && !self.hasNoAutoPause) { self.autoPaused = true; self.pause(); } }, { root: null, threshold: [0, 1] // Get events when it is shown and hidden }).observe($container.get(0)); }; /** * Attaches the video handler to the given container. * Inserts text if no handler is found. * * @public * @param {jQuery} $container */ self.attach = function ($container) { $container.addClass('h5p-video h5p-theme').html(''); if (self.appendTo !== undefined) { self.appendTo($container); // Avoid autoplaying in authoring tool if (window.H5PEditor === undefined) { handleAutoPlayPause($container); } } else if (sources.length) { $container.text(parameters.l10n.noPlayers); } else { $container.text(parameters.l10n.noSources); } }; /** * Get name of the video handler * * @public * @returns {string} */ self.getHandlerName = function() { return handlerName; }; /** * @public * Get current state for resume support. * * @returns {object} Current state. */ self.getCurrentState = function () { if (self.getCurrentTime) { return { time: self.getCurrentTime() || self.oldTime, }; } }; /** * The two functions below needs to be defined in this base class, * since it is used in this class even if no handler was found. */ self.seek = () => {}; self.pause = () => {}; /** * @public * Reset current state (time). * */ self.resetTask = function () { delete self.oldTime; self.resetPlayback(parameters.startAt || 0); }; /** * Default implementation of resetPlayback. May be overridden by sub classes. * * @param {*} startAt */ self.resetPlayback = startAt => { self.seek(startAt); self.pause(); self.WAS_RESET = true; }; // Resize the video when we know its aspect ratio self.on('loaded', function () { self.trigger('resize'); // reset time if wasn't done immediately if (self.WAS_RESET) { self.seek(parameters.startAt || 0); if (!parameters.playback.autoplay) { self.pause(); } self.WAS_RESET = false; } }); // Find player for video sources if (sources.length) { const options = { controls: parameters.visuals.controls, autoplay: parameters.playback.autoplay, loop: parameters.playback.loop, fit: parameters.visuals.fit, poster: parameters.visuals.poster === undefined ? undefined : parameters.visuals.poster, tracks: tracks, disableRemotePlayback: parameters.visuals.disableRemotePlayback === true, disableFullscreen: parameters.visuals.disableFullscreen === true, deactivateSound: parameters.playback.deactivateSound, } if (!self.WAS_RESET) { options.startAt = self.oldTime !== undefined ? self.oldTime : (parameters.startAt || 0); } var html5Handler; for (var i = 0; i < handlers.length; i++) { var handler = handlers[i]; if (handler.canPlay !== undefined && handler.canPlay(sources)) { handler.call(self, sources, options, parameters.l10n); handlerName = handler.name; return; } if (handler === H5P.VideoHtml5) { html5Handler = handler; handlerName = handler.name; } } // Fallback to trying HTML5 player if (html5Handler) { html5Handler.call(self, sources, options, parameters.l10n); } } } // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); Video.prototype.constructor = Video; // Player states /** @constant {Number} */ Video.ENDED = 0; /** @constant {Number} */ Video.PLAYING = 1; /** @constant {Number} */ Video.PAUSED = 2; /** @constant {Number} */ Video.BUFFERING = 3; /** * When video is queued to start * @constant {Number} */ Video.VIDEO_CUED = 5; // Used to convert between html and text, since URLs have html entities. var $cleaner = H5P.jQuery('
'); /** * Help keep track of key value pairs used by the UI. * * @class * @param {string} label * @param {string} value */ Video.LabelValue = function (label, value) { this.label = label; this.value = value; }; /** * Determine whether video can be autoplayed. * @returns {Promise} Whether autoplay is allowed. */ Video.isAutoplayAllowed = async () => { if (document.featurePolicy?.allowsFeature('autoplay')) { return true; // Browser supports `featurePolicy` and can tell directly } const video = document.createElement('video'); /* * Without a video source, the play Promise will be rejected with an error * if it cannot be autoplayed, but not resolve at all if it can be * autoplayed. Using a timeout to detect the latter case here. */ const timeoutMs = 50; // If play promise rejects, then within few ms const timeoutPromise = new Promise((resolve) => { window.setTimeout(() => { resolve(true); // Timeout reached, autoplay is allowed }, timeoutMs); }); let result; try { result = (await Promise.race([video.play(), timeoutPromise])) ?? true; } catch (error) { result = false; } return result; }; /** @constant {Boolean} */ Video.IE11_PLAYBACK_RATE_FIX = (navigator.userAgent.match(/Trident.*rv[ :]*11\./) ? true : false); return Video; })(H5P.jQuery, H5P.ContentCopyrights, H5P.MediaCopyright, H5P.videoHandlers || []); ; (()=>{"use strict";const t=function(t){const e=t.length;return function o(){const n=Array.prototype.slice.call(arguments,0);return n.length>=e?t.apply(null,n):function(){const t=Array.prototype.slice.call(arguments,0);return o.apply(null,n.concat(t))}}},e=t((function(t,e){e.forEach(t)})),o=(t((function(t,e){return e.map(t)})),t((function(t,e){return e.filter(t)}))),n=(t((function(t,e){return e.some(t)})),t((function(t,e){return-1!=e.indexOf(t)}))),i=t((function(t,e){return o((e=>!n(e,t)),e)})),s=t(((t,e)=>e.getAttribute(t))),r=t(((t,e,o)=>o.setAttribute(t,e))),a=t(((t,e)=>e.removeAttribute(t))),c=t(((t,e)=>e.hasAttribute(t))),l=(t(((t,e,o)=>o.getAttribute(t)===e)),t(((t,e)=>{const o=s(t,e);r(t,("true"!==o).toString(),e)})),t(((t,e)=>t.appendChild(e))),t(((t,e)=>e.querySelector(t))),t(((t,e)=>{return o=e.querySelectorAll(t),Array.prototype.slice.call(o);var o})),t(((t,e)=>t.removeChild(e))),t(((t,e)=>e.classList.contains(t))),t(((t,e)=>e.classList.add(t)))),d=t(((t,e)=>e.classList.remove(t))),u=l("hidden"),h=d("hidden"),p=(t(((t,e)=>(t?h:u)(e))),t(((t,e,o)=>{o.classList[e?"add":"remove"](t)})),a("tabindex")),m=(e(p),r("tabindex","0")),v=r("tabindex","-1"),f=c("tabindex");class b{constructor(t){Object.assign(this,{listeners:{},on:function(t,e,o){const n={listener:e,scope:o};return this.listeners[t]=this.listeners[t]||[],this.listeners[t].push(n),this},fire:function(t,e){return(this.listeners[t]||[]).every((function(t){return!1!==t.listener.call(t.scope||this,e)}))},propagate:function(t,e){let o=this;t.forEach((t=>e.on(t,(e=>o.fire(t,e)))))}}),this.plugins=t||[],this.elements=[],this.negativeTabIndexAllowed=!1,this.on("nextElement",this.nextElement,this),this.on("previousElement",this.previousElement,this),this.on("firstElement",this.firstElement,this),this.on("lastElement",this.lastElement,this),this.initPlugins()}addElement(t){this.elements.push(t),this.firesEvent("addElement",t),1===this.elements.length&&this.setTabbable(t)}insertElementAt(t,e){this.elements.splice(e,0,t),this.firesEvent("addElement",t),1===this.elements.length&&this.setTabbable(t)}removeElement(t){this.elements=i([t],this.elements),f(t)&&(this.setUntabbable(t),this.elements[0]&&this.setTabbable(this.elements[0])),this.firesEvent("removeElement",t)}count(){return this.elements.length}firesEvent(t,e){const o=this.elements.indexOf(e);return this.fire(t,{element:e,index:o,elements:this.elements,oldElement:this.tabbableElement})}nextElement({index:t}){const e=t===this.elements.length-1,o=this.elements[e?0:t+1];this.setTabbable(o),o.focus()}firstElement(){const t=this.elements[0];this.setTabbable(t),t.focus()}lastElement(){const t=this.elements[this.elements.length-1];this.setTabbable(t),t.focus()}setTabbableByIndex(t){const e=this.elements[t];e&&this.setTabbable(e)}setTabbable(t){e(this.setUntabbable.bind(this),this.elements),m(t),this.tabbableElement=t}setUntabbable(t){t!==document.activeElement&&(this.negativeTabIndexAllowed?v(t):p(t))}previousElement({index:t}){const e=0===t,o=this.elements[e?this.elements.length-1:t-1];this.setTabbable(o),o.focus()}useNegativeTabIndex(){this.negativeTabIndexAllowed=!0,this.elements.forEach((t=>{t.hasAttribute("tabindex")||v(t)}))}initPlugins(){this.plugins.forEach((function(t){void 0!==t.init&&t.init(this)}),this)}}class g{constructor(){this.selectability=!0}init(t){this.boundHandleKeyDown=this.handleKeyDown.bind(this),this.controls=t,this.controls.on("addElement",this.listenForKeyDown,this),this.controls.on("removeElement",this.removeKeyDownListener,this)}listenForKeyDown({element:t}){t.addEventListener("keydown",this.boundHandleKeyDown)}removeKeyDownListener({element:t}){t.removeEventListener("keydown",this.boundHandleKeyDown)}handleKeyDown(t){switch(t.which){case 27:this.close(t.target),t.preventDefault(),t.stopPropagation();break;case 35:this.lastElement(t.target),t.preventDefault(),t.stopPropagation();break;case 36:this.firstElement(t.target),t.preventDefault(),t.stopPropagation();break;case 13:case 32:this.select(t.target),t.preventDefault(),t.stopPropagation();break;case 37:case 38:this.hasChromevoxModifiers(t)||(this.previousElement(t.target),t.preventDefault(),t.stopPropagation());break;case 39:case 40:this.hasChromevoxModifiers(t)||(this.nextElement(t.target),t.preventDefault(),t.stopPropagation())}}hasChromevoxModifiers(t){return t.shiftKey||t.ctrlKey}previousElement(t){!1!==this.controls.firesEvent("beforePreviousElement",t)&&(this.controls.firesEvent("previousElement",t),this.controls.firesEvent("afterPreviousElement",t))}nextElement(t){!1!==this.controls.firesEvent("beforeNextElement",t)&&(this.controls.firesEvent("nextElement",t),this.controls.firesEvent("afterNextElement",t))}select(t){this.selectability&&!1!==this.controls.firesEvent("before-select",t)&&(this.controls.firesEvent("select",t),this.controls.firesEvent("after-select",t))}firstElement(t){!1!==this.controls.firesEvent("beforeFirstElement",t)&&(this.controls.firesEvent("firstElement",t),this.controls.firesEvent("afterFirstElement",t))}lastElement(t){!1!==this.controls.firesEvent("beforeLastElement",t)&&(this.controls.firesEvent("lastElement",t),this.controls.firesEvent("afterLastElement",t))}disableSelectability(){this.selectability=!1}enableSelectability(){this.selectability=!0}close(t){!1!==this.controls.firesEvent("before-close",t)&&(this.controls.firesEvent("close",t),this.controls.firesEvent("after-close",t))}}var y=0,k=1,S=function(t,e,o,n,i,s){var r=this,a="interactive-video-".concat(s,"-menu-").concat(t),c=new b([new g]);c.on("close",(function(){return u()}));var l,d=[];H5P.EventDispatcher.call(r);var u=function(){r.control.setAttribute("aria-expanded","false"),r.control.focus(),r.popup.classList.remove("h5p-show"),r.trigger("close")},h=function(){"true"===r.control.getAttribute("aria-expanded")?u():(r.control.setAttribute("aria-expanded","true"),r.popup.classList.add("h5p-show"),r.trigger("open"),r.popup.querySelector('li[tabindex="0"]').focus())},p=function(t){var e=t.value===o.value,i=w(null,k,t.label,(function(){!function(t,e){o=e,function(t,e,o){for(var n=0;n').concat(i[t],"")),r.popup.setAttribute("role","dialog");var m=w("h5p-chooser-close-button",y,i.close,h,"div","button");r.popup.appendChild(m),r.control=w("h5p-control h5p-"+t,y,i[t],h,"div","button"),r.control.setAttribute("aria-haspopup","true"),r.overlayControl=w("h5p-minimal-button h5p-"+t,k,i[t],h,"div","menuitem"),r.overlayControl.tabIndex="-1",r.updateOptions(e)};(S.prototype=Object.create(H5P.EventDispatcher.prototype)).constructor=S;var $=function(t,e,o){var n=document.createElement(o||"div");return t&&(n.className=t),e&&(n.innerHTML=e),n},w=function(t,e,o,n,i,s){var r=$(t,e===k?o:"",i);return r.tabIndex=0,r.setAttribute("role",s),e===y&&(r.title=o),r.addEventListener("click",(function(t){n.call(r,t)}),!1),r.addEventListener("keydown",(function(t){32!==t.which&&13!==t.which||(t.preventDefault(),n.call(r,t))}),!1),r};const T=S,C={ENTER:13,SPACE:32,ARROW_LEFT:37,ARROW_UP:38,ARROW_RIGHT:39,ARROW_DOWN:40,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90},x=(t,e,o={ctrl:!1,shift:!1})=>-1!==e.indexOf(t.which)&&(!(o.ctrl&&!t.ctrlKey)&&!(o.shift&&!t.shiftKey)),P=t=>-1!==[C.ENTER,C.SPACE].indexOf(t.which),I=(t,e,o)=>{t.on("keydown",(t=>{for(let n=0;n0},A=function(t){return M(t)?B("
".concat(t,"
")).text():void 0},H="timecode",D=function(t,e){return void 0!==t.goto&&t.goto.type===e},R=["H5P.Image","H5P.Nil","H5P.Table","H5P.Link","H5P.GoToQuestion","H5P.IVHotspot","H5P.Text"],V=["H5P.Text","H5P.Table"],O=function(t){return-1!==V.indexOf(t)};function q(t,e,o){var n,i,s,r,a=this;H5P.EventDispatcher.call(a);var c=t.action;o&&(c.userDatas={state:o});var l,d,u,h,p=c.library.split(" ")[0],m=e.l10n[function(t,e){return-1!==R.indexOf(t)||D(e,H)}(p,t)?"content":"interaction"],v="H5P.Nil"!==p&&"button"===t.displayType,f=[c.params.contentName,v?A(t.label):"","H5P.Link"===p&&void 0!==c.params.title?c.params.title:t.libraryTitle].filter(M)[0],b=!1,g=!1,y=!1,k=t.action.metadata;this.on("open-dialog",(function(){P()})),this.on("show-mask",(function(){L(this.getElement())}));var S=function(o){n=B("
",{tabindex:0,role:"button",class:"h5p-interaction "+l+(o?"":" h5p-hidden"),"aria-haspopup":"dialog","aria-expanded":"false","aria-label":f,css:{left:t.x+"%",top:t.y+"%",width:"",height:""},on:{click:function(){a.dialogDisabled||(P(),n.attr("aria-expanded","true"))},keydown:function(t){13!==t.which&&32!==t.which||a.dialogDisabled||(P(),n.attr("aria-expanded","true"),t.preventDefault())}}}),a.getRequiresCompletion()&&void 0===e.editor&&e.currentState!==H5P.InteractiveVideo.SEEKING&&P(!0),B("
",{class:"h5p-touch-area"}).appendTo(n),B("
",{class:"h5p-interaction-button"}).appendTo(n),e.editor&&n.hover((function(){n.is(".focused")||n.is(":focus")||e.dnb&&(!e.dnb||e.dnb.newElement)?(e.editor.hideInteractionTitle(),b=!1):(e.editor.showInteractionTitle(f,n),b=!0)}),(function(){e.editor.hideInteractionTitle(),b=!1})).focus((function(){e.editor.hideInteractionTitle(),b=!1})).click((function(){e.editor.hideInteractionTitle()}));var s=M(A(t.label));t.label&&s&&(i=$(t.label,"h5p-interaction").appendTo(n)),a.trigger("display",n),setTimeout((function(){n&&n.removeClass("h5p-hidden")}),0)},$=function(t){return B("
",{class:"h5p-interaction-label ".concat(arguments.length>1&&void 0!==arguments[1]?arguments[1]:""),html:'
'.concat(t,"
")})},w=function(){return(n=$(t.label,"h5p-interaction h5p-interaction-label-standalone")).css({left:"".concat(t.x,"%"),top:"".concat(t.y,"%"),width:"",height:"initial"}),a.trigger("display",n),setTimeout((function(){n&&n.removeClass("h5p-hidden")}),0),n},T=function(e){if("timecode"===t.goto.type)e.click((function(e){1===e.which&&V({data:t.goto.time})})).keypress((function(e){32===e.which&&V({data:t.goto.time})})).attr("href","#").attr("tabindex","0");else{var o=t.goto.url;e.keypress((function(t){32===t.which&&this.click()})).attr({href:("other"!==o.protocol?o.protocol:"")+o.url,target:"_blank"})}return e.addClass("goto-clickable")},C=function(t){var o=!e.hasUncompletedRequiredInteractions(t);d&&d.trigger("hide"),n&&a.trigger("hide",n),a.isButton()?o&&e.dnb.dialog.close():(e.isMobileView&&o&&e.dnb.dialog.close(),n&&n.detach()),a.trigger("remove",n),o&&W(n)},x=function(t){if("H5P.Questionnaire"===p){if(t.find(".h5p-interaction-continue-button").length)return;var o=function(){var t=document.createElement("button");return t.innerHTML=e.l10n.continueWithVideo,t.className="h5p-interaction-continue-button",t.addEventListener("click",(function(){C(),e.play()})),t}(),n=t.find(".h5p-questionnaire-success-center");n.length&&n.get(0).appendChild(o),d.on("noSuccessScreen",(function(){C(),e.play()}))}"H5P.FreeTextQuestion"===p&&d.on("continue",(function(){C(),e.play()}))},P=function(o){var i=e.$container.find(".h5p-dialog-wrapper"),s=i.find(".h5p-dialog-titlebar");"function"==typeof d.setActivityStarted&&"function"==typeof d.getScore&&d.setActivityStarted();var r=a.isGotoClickable(),l=i.find("[tabindex]"),u=B(r?"":"
",{class:"h5p-dialog-interaction h5p-frame"});!r&&O(p)&&u.attr("tabindex","0"),void 0!==e.editor?u.attr("tabindex",-1):0===l.length&&u.attr("tabindex",0),a.getRequiresCompletion()&&i.keydown((function(t){!function(t,e){var o=function(t){return e.target===t.get(0)},n=9===e.which,i=t.first(),s=t.last();n&&e.shiftKey&&o(i)?(s.focus(),e.preventDefault()):n&&o(s)&&(i.focus(),e.preventDefault())}(i.find('[tabindex="0"], button, input').filter(":visible"),t)}));var h=r?T(u):u;if(d.attach(h),x(h),K(d)&&(a.score=d.getScore(),a.maxScore=d.getMaxScore()),a.hasFullScore()&&o){if(i.hasClass("h5p-hidden"))return}else a.getRequiresCompletion()&&!a.hasFullScore()&&(e.dnb.dialog.hideCloseButton(),e.dnb.dialog.disableOverlay=!0,i.click((function(){a.hasFullScore()||e.showWarningMask().find(".h5p-button-back").click((function(){return u.find("button").last().focus()}))})));e.dnb.dialog.open(u),e.disableTabIndexes(),e.dnb.dialog.addLibraryClass(p),e.dnb.dialog.toggleClass("goto-clickable-visualize",!(!r||!t.goto.visualize)),e.dnb.dialog.toggleClass("h5p-goto-timecode",D(t,H)),e.dnb.dialog.disableOverlay&&e.restorePosterTabIndexes();e.dnb.dialog.once("close",(function(){e.dnb.$dialogContainer.one("transitionend",(function(){u.is(".h5p-image")&&u.find("img").css({width:"",height:""})}));try{void 0!==d.pause&&(d.pause instanceof Function||"function"==typeof d.pause)&&d.pause()}catch(t){H5P.error(t)}n&&(e.dnb.calledFromResetTask||n.focus(),n.attr("aria-expanded","false")),d&&d.trigger("hide");var t=i.find(".h5p-dialog-titlebar").find(".h5p-sc-sound-control");t.length&&i.find(".h5p-sc-set-wrapper").append(t)}));if(e.dnb.dialog.on("close",(function t(){a.dialogWidth=e.dnb.dialog.getDialogWidth(),e.dnb.dialog.off("close",t)})),"H5P.Image"===p){var m=e.dnb.dialog.getMaxSize(n),v=u.find("img");c.params.file.width&&c.params.file.height?I(v,m,{width:c.params.file.width,height:c.params.file.height},!e.isMobileView):(v.on("load",(function(){v.is(":visible")&&I(v,m,{width:this.width,height:this.height},!e.isMobileView)})),e.dnb.dialog.position(n))}else if(!e.isMobileView){var f=null;"H5P.FreeTextQuestion"===p?f="big":"H5P.Text"!==p&&"H5P.Table"!==p&&(f="medium"),e.dnb.dialog.position(n,{width:a.dialogWidth/16},f)}if("H5P.Summary"===p){var b=0;H5P.on(d,"resize",(function(){var t=u.height();(b>t+10||bo.height&&(i.width=i.width*o.height/i.height,i.height=o.height),i.width>o.width&&(i.height=i.height*o.width/i.width,i.width=o.width);var r=16/Number(t.css("fontSize").replace("px",""));t.css({width:i.width*r+"em",height:i.height*r+"em"}),s&&e.dnb.dialog.position(n,i)},V=function(o){a.isButton()&&e.dnb.dialog.close(),e.currentState!==H5P.Video.PAUSED&&e.currentState!==H5P.Video.ENDED||e.play();var n=o.data;n===t.duration.from&&(n+=.2),e.seek(n,{force:!0})},q=function(){var o=t.height||10,n=t.width||10,i=e.width/e.fontSize;return{height:o/(i/(e.$videoWrapper.width()/e.$videoWrapper.height()))*100+"%",width:n/i*100+"%"}},L=function(t){t.css("zIndex",52),e.showOverlayMask()},W=function(t){t&&t.css("zIndex",""),e.hideOverlayMask()},N=function(){var o=a.isGotoClickable(),i=q(),c=B.extend({},{backgroundColor:"rgb(255,255,255)",boxShadow:!0},t.visuals);if(n=B("
",{"aria-label":e.l10n.interaction,tabindex:"-1",class:"h5p-interaction h5p-poster "+l+(o&&t.goto.visualize?" goto-clickable-visualize":""),css:{left:t.x+"%",top:t.y+"%",width:i.width,height:i.height}}),"H5P.IVHotspot"!==p){n.css("background",c.backgroundColor);var u=c.backgroundColor.split(",");if(u[3])0===parseFloat(u[3].replace(")",""))&&n.addClass("h5p-transparent-interaction")}!1===c.boxShadow&&n.addClass("h5p-box-shadow-disabled"),"H5P.Link"===p&&(n.css("height","auto"),n.css("width","auto"),void 0===e.editor&&n.click((function(){return window.open(d.getUrl()),e.pause(),!1}))),r=B("
",{class:"h5p-interaction-outer"}).appendTo(n),s=B(o?"":"
",{class:"h5p-interaction-inner h5p-frame"}).appendTo(r),!o&&O(p)&&s.attr("tabindex","0"),void 0!==e.editor&&d.disableAutoPlay&&d.disableAutoPlay();var h=o?T(s):s;d.attach(h),x(h),a.trigger("display",n),a.getRequiresCompletion()&&e.currentState!==H5P.InteractiveVideo.SEEKING&&void 0===e.editor&&!a.hasFullScore()&&(L(n),n.focus()),setTimeout((function(){H5P.trigger(d,"resize")}),0),"function"==typeof d.setActivityStarted&&"function"==typeof d.getScore&&d.setActivityStarted()},F=function(){var o,i,s=!0;if(t.adaptivity&&(i=a.hasFullScore(),s=!a.getRequiresCompletion()||i,i?o=t.adaptivity.correct:i||(o=t.adaptivity.wrong)),o&&void 0!==o.seekTo){e.pause(),!o.allowOptOut&&n&&(a.isButton()?(e.dnb.dialog.disableOverlay=!0,e.dnb.dialog.hideCloseButton()):L(n));var r=i?"correct":"wrong",c=o.seekLabel?o.seekLabel:e.l10n.defaultAdaptivitySeekLabel;d.hideButton("iv-continue").addButton("iv-adaptivity-"+r,c,(function(){C(o.seekTo),!i&&d.resetTask&&(d.resetTask(),d.hideButton("iv-adaptivity-"+r)),a.remove(),z(o.seekTo)})).showButton("iv-adaptivity-"+r,1).hideButton("iv-adaptivity-"+(i?"wrong":"correct"),1).hideButton("check-answer",1).hideButton("show-solution",1).hideButton("try-again",1),void 0!==d.disableInput&&(d.disableInput instanceof Function||"function"==typeof d.disableInput)&&d.disableInput(),setTimeout((function(){var t=o.message.replace("

","").replace("

","");d.updateFeedbackContent(t,!0),d.read(t)}),0)}else void 0!==d.hasButton&&(d.hasButton("iv-continue")||d.addButton("iv-continue",e.l10n.defaultAdaptivitySeekLabel,(function(){C(),z()})),d[s?"showButton":"hideButton"]("iv-continue"))},z=function(t){var o=U(),n=o.filter((function(t){return!t.isButton()}));if(n.length?o=n:o.length&&e.$container.find(".h5p-dialog-wrapper .h5p-dialog").show(),o.length){var i=o[0];return i.isButton()?i.trigger("open-dialog"):i.trigger("show-mask"),void e.pause()}e.currentState!==H5P.Video.ENDED&&(void 0!==t&&(e.pause(),e.seek(t,{force:!0})),e.play(),e.controls.$play.focus())},U=function(){return e.getVisibleInteractions().filter((function(t){return t!==a})).filter((function(t){return t.getRequiresCompletion()&&!t.hasFullScore()}))};a.isGotoClickable=function(){return-1!==["H5P.Text","H5P.Image"].indexOf(p)&&t.goto&&-1!==["timecode","url"].indexOf(t.goto.type)},a.getCurrentState=function(){if(d&&(d.getCurrentState instanceof Function||"function"==typeof d.getCurrentState))return d.getCurrentState()},a.getDuration=function(){return{from:t.duration.from,to:t.duration.to+1}},a.getRequiresCompletion=function(){return!!t.adaptivity&&!!t.adaptivity.requireCompletion},a.pause=function(){return t.pause},a.isButton=function(){return"button"===t.displayType||!(!e.isMobileView||"H5P.IVHotspot"===p)&&("H5P.Image"!==p||!1!==t.buttonOnMobile)},a.isStandaloneLabel=function(){return"H5P.Nil"===p},a.isMainSummary=function(){return!0===t.mainSummary},a.selectDot=function(){e.isSkippingProhibited(t.duration.from)?e.showPreventSkippingMessage({x:t.duration.from/e.video.getDuration()*e.controls.$slider.get(0).offsetWidth,y:-13},e.l10n.navForwardDisabled):(e.seekingTo=!0,Math.floor(10*e.video.getCurrentTime())!==Math.floor(10*t.duration.from)&&(e.currentState===H5P.Video.VIDEO_CUED?(e.play(),e.seek(t.duration.from)):e.currentState===H5P.Video.PLAYING?e.seek(t.duration.from):(e.play(),e.seek(t.duration.from),e.pause())))},a.addDot=function(){if("H5P.Nil"===p)return B("
",{class:o});var o="h5p-seekbar-interaction "+l,n=B("
",{role:"menuitem",class:o,"aria-label":"".concat(m,". ").concat(f),title:f,css:{left:t.duration.from*e.oneSecondInPercentage+"%"},on:{click:a.selectDot,keydown:function(t){if(13===t.which||32===t.which)return a.selectDot(),!1}}});return e.preventSkipping&&n.attr("aria-disabled","true").attr("tabindex","-1"),a.$menuitem=n,n},a.isVisible=function(){return y},a.visibleAt=function(e){return!(e=t.duration.to+1)},a.toggle=function(o,i){return a.visibleAt(o)?n?void 0:(y=!0,a.isStandaloneLabel()?w():a.isButton()?S(i):N(),void 0===e.editor?h=e.dnb.add(n,void 0,{dnbElement:h,disableContextMenu:!0}):(a.fit&&(e.editor.fit(n,t),a.fit=!1),n.focus((function(){e.pause()}))),n):(y=!1,void(n&&(h&&(h.hideContextMenu(),h===e.dnb.focusedElement&&(h.blur(),delete e.dnb.focusedElement)),e.editor&&b&&(e.editor.hideInteractionTitle(),b=!1),a.remove())))},a.setTitle=function(t){n&&n.attr("aria-label",t),f=t},a.reCreateInteraction=function(){"H5P.IVHotspot"!==p&&n&&(d&&d.trigger("hide"),a.trigger("hide",n),n.detach(),a.isStandaloneLabel()?w():a.isButton()?S(!0):N())},a.resizeInteraction=function(){a.isStandaloneLabel()||H5P.trigger(d,"resize")},a.positionLabel=function(t){n&&a.isButton()&&i&&!a.isStandaloneLabel()&&(i.removeClass("h5p-left-label"),parseInt(n.css("left"))+i.position().left+i.outerWidth()>t&&i.addClass("h5p-left-label"))},a.setPosition=function(e,o){t.x=e,t.y=o,n.css({left:e+"%",top:o+"%"})},a.setSize=function(e,o){e&&(t.width=e),o&&(t.height=o),H5P.trigger(d,"resize")},a.remove=function(){n&&(a.trigger("domHidden",{$dom:n,key:"videoProgressedPast"},{bubbles:!0,external:!0}),d&&d.trigger("hide"),a.trigger("hide",n),n.detach(),n=void 0)},a.reCreate=function(){l=function(){var e=t.className;if(void 0===e){var o=c.library.split(" ")[0].toLowerCase().split(".");e=o[0]+"-"+o[1]+"-interaction"}return t.goto&&"timecode"===t.goto.type&&(e+=" h5p-goto-timecode"),e}(),a.isStandaloneLabel()||(c.params=c.params||{},d=H5P.newRunnable(c,e.contentId,void 0,void 0,{parent:e,editing:void 0!==e.editor}),void 0===a.maxScore&&d.getMaxScore&&(a.maxScore=d.getMaxScore()),c.userDatas&&K(d)&&(a.score=d.getScore()),e.isTask||void 0===e.options.assets.endscreens||(d.isTask||void 0===d.isTask&&void 0!==d.showSolutions)&&(e.isTask=!0),d.on&&(d.on("xAPI",(function(t){var o=t.getVerifiedStatementValue(["context","contextActivities","parent"])||[],n=t.getContentXAPIId(e),i="completed"===t.getVerb()||"answered"===t.getVerb(),s=o.some((function(t){return t.id===n}));d.getScore&&(a.score=d.getScore()),d.getMaxScore&&(a.maxScore=d.getMaxScore()),s&&i&&t.getMaxScore()&&(a.score=null==t.getScore()?0:t.getScore(),a.maxScore=a.maxScore?a.maxScore:t.getMaxScore(),F()),a.setLastXAPIVerb(t.getVerb()),a.trigger(t)})),d.on("question-finished",(function(){F()})),d.on("resize",(function(){delete a.dialogWidth,e&&e.dnb&&e.dnb.dialog.removeStaticWidth()})),"H5P.IVHotspot"===p&&d.on("goto",V),"H5P.GoToQuestion"===p&&d.on("chosen",V)))};var K=function(t){return"undefined"!==E(t)&&"function"==typeof t.getScore&&"function"==typeof t.getMaxScore};a.setDnbElement=function(t){return h!==t&&(h=t,!0)},a.hasFullScore=function(){return a.score>=a.maxScore},a.getLibraryName=function(){return p},a.getMetadata=function(){return k},a.getTitle=function(){return f},a.isAnswerable=function(){return-1===R.indexOf(a.getLibraryName())&&!a.isStandaloneLabel()},a.setProgress=function(t){this.progress=t},a.getProgress=function(){return this.progress},a.setLastXAPIVerb=function(t){u=t},a.getLastXAPIVerb=function(){return u},a.getClass=function(){return l},a.getCopyrights=function(){if(!a.isStandaloneLabel()){var o=H5P.newRunnable(c,e.contentId);if(void 0!==o){var n=new H5P.ContentCopyrights;return n.addContent(H5P.getCopyrights(o,t,e.contentId)),n.setLabel(f+" "+H5P.InteractiveVideo.humanizeTime(t.duration.from)+" - "+H5P.InteractiveVideo.humanizeTime(t.duration.to)),n}}},a.getXAPIData=function(){if(d&&(d.getXAPIData instanceof Function||"function"==typeof d.getXAPIData))return d.getXAPIData()},a.getSubcontentId=function(){return c.subContentId},a.getElement=function(){return n},a.focusOnFirstTabbableElement=function(){if(n){var t=B(n.get(0)).find("[tabindex]");t&&t.length?t.get(0).focus():a.focus()}},a.focus=function(){n&&n.focus()},a.getClipboardData=function(){return H5P.DragNBar.clipboardify(H5PEditor.InteractiveVideo.clipboardKey,t,"action")},a.repositionToWrapper=function(e){if(n&&"H5P.IVHotspot"!==p&&"H5P.FreeTextQuestion"!==p){if(g&&(n.css({top:t.y+"%",left:t.x+"%"}),n.css(a.isButton()?{height:"",width:""}:q()),g=!1),n.position().top+n.height()>e.height()){var o=(e.height()-n.height())/e.height()*100;if(o<0){o=0;var i=e.height()/parseFloat(n.css("font-size"));n.css("height",i+"em")}n.css("top",o+"%"),g=!0}if(n.position().left+n.width()>e.width()){var s=(e.width()-n.width())/e.width()*100;if(s<0){s=0;var r=e.width()/parseFloat(n.css("font-size"));n.css("width",r+"em")}n.css("left",s+"%"),g=!0}}},a.resetTask=function(){var t;void 0!==c.userDatas&&void 0!==c.userDatas.state&&delete c.userDatas.state,delete a.score,delete a.maxScore,delete a.progress,"function"==typeof(null===(t=a.getInstance())||void 0===t?void 0:t.resetTask)&&a.getInstance().resetTask(),a.reCreate()},a.getInstance=function(){return d},a.reCreate()}q.prototype=Object.create(H5P.EventDispatcher.prototype),q.prototype.constructor=q,q.PROGRESS_INTERACTED=0,q.PROGRESS_ANSWERED=1;const L=q;var W=function(){function t(t){this.l10n=t;var e=document.createElement("div");e.classList.add("h5p-iv-interactions-announcer"),e.setAttribute("aria-live","polite"),this.interactionsAnnouncer=e}var e=t.prototype;return e.getInteractionAnnouncer=function(){return this.interactionsAnnouncer},e.announceInteractions=function(t){t.length>0&&(this.interactionsAnnouncer.textContent="",this.interactionsAnnouncer.textContent="\n ".concat(this.getAnnouncementMessage(t.length),"\n ").concat(this.getTitleAnnouncement(t.length,t[0]),"\n ").concat(this.getPauseAnnouncement(t)))},e.getAnnouncementMessage=function(t){return 0===t?"":1===t?this.l10n.singleInteractionAnnouncement:this.l10n.multipleInteractionsAnnouncement},e.getTitleAnnouncement=function(t,e){return 1===t?e.getTitle():""},e.getPauseAnnouncement=function(t){return t.some((function(t){return t.pause()}))?". ".concat(this.l10n.videoPausedAnnouncement):""},t}(),N=H5P.jQuery,F=!!navigator.userAgent.match(/iPod|iPhone|iPad/g),z=function(){function t(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{content:"",maxWidth:"auto",style:"h5p-interactive-video-bubble",mode:"centered",focus:function(){}};this.$reference=t,this.maxWidth=e.maxWidth,this.style=e.style,this.mode=e.mode,this.focus=e.focus,this.$tail=N("
",{class:"".concat(this.style,"-tail")}),this.$innerTail=N("
",{class:"".concat(this.style,"-inner-tail")}),this.$content=N("
",{class:"".concat(this.style,"-text")}),"string"==typeof e.content?this.$content.html(e.content):this.$content.append(e.content),this.$innerBubble=N("
",{class:"".concat(this.style,"-inner")}).append(this.$content).prepend(this.$innerTail),this.$h5pContainer=this.$reference.closest(".h5p-interactive-video"),this.$bubble=N("
",{class:this.style,"aria-live":"polite"}).append([this.$tail,this.$innerBubble]).addClass("".concat(this.style,"-inactive")).appendTo(this.$h5pContainer),F&&H5P.$body.css("cursor","pointer"),"centered"===this.mode&&this.$bubble.css({width:"auto"===this.maxWidth?"auto":"".concat(this.maxWidth,"px")}),this.update()}var e=t.prototype;return e.update=function(){var t=this,e=this.getOffsetBetween(this.$h5pContainer,this.$reference),o="full"===this.mode?this.$bubble.outerWidth():Math.min(.9*e.outerWidth,"auto"===this.maxWidth?this.$bubble.outerWidth():this.maxWidth),n=this.getBubblePosition(o,e,this.mode);"centered"===this.mode&&this.$bubble.css({bottom:void 0===n.bottom?void 0:"".concat(n.bottom,"px"),left:"".concat(n.left,"px")}),setTimeout((function(){var e=t.getTailPosition(t.$reference),o={bottom:"".concat(e.bottom,"px"),left:"string"==typeof e.left?e.left:"".concat(e.left,"px")};t.$tail.css(o),t.$innerTail.css(o)}),75)},e.animate=function(){var t=this;this.$bubble.hasClass("".concat(this.style,"-inactive"))&&(this.$bubble.removeClass("".concat(this.style,"-inactive")).addClass("".concat(this.style,"-active")),setTimeout((function(){t.$bubble.removeClass("".concat(t.style,"-active")).addClass("".concat(t.style,"-inactive"))}),2e3))},e.setContent=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";this.$content.html(t),this.update()},e.getContent=function(){return this.$content.get(0).outerHTML},e.isActive=function(){return this.$bubble.hasClass("".concat(this.style,"-active"))},e.toggle=function(t){var e=this,o=arguments.length>1&&void 0!==arguments[1]&&arguments[1];(t=void 0===t?!this.isActive():t)&&o?(setTimeout((function(){e.$bubble.removeClass("".concat(e.style,"-preparing")).addClass("".concat(e.style,"-active")),setTimeout((function(){return e.focus()}),400)}),100),this.$bubble.removeClass("".concat(this.style,"-inactive")).addClass("".concat(this.style,"-preparing"))):this.$bubble.toggleClass("".concat(this.style,"-inactive"),!t).toggleClass("".concat(this.style,"-active"),t),this.update()},e.getBubblePosition=function(t,e,o){var n=t/2;return{bottom:"full"===o?void 0:e.bottom+e.innerHeight+4,left:"full"===o?(e.outerWidth-t)/2:e.left-n+16}},e.getTailPosition=function(t){return{left:t.offset().left-this.$tail.parent().offset().left+8,top:-6,bottom:-6}},e.getOffsetBetween=function(t,e){var o=t[0].getBoundingClientRect(),n=e[0].getBoundingClientRect();return{top:n.top-o.top,right:o.right-n.right,bottom:o.bottom-n.bottom,left:n.left-o.left+parseInt(e.css("marginLeft")),innerWidth:n.width,innerHeight:n.height,outerWidth:o.width,outerHeight:o.height}},e.fullscreen=function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,n=this.isMobilePhone(),i={maxHeight:"",top:""};t&&!n&&void 0!==e&&void 0!==o&&(i.maxHeight="calc(".concat(o,"px - 1em - 9px)"),i.top="calc(((".concat(e-o,"px + 1em) / 2) - 9px)")),this.$bubble.toggleClass("mobile-fullscreen",n&&t),this.$bubble.css(i)},e.isMobilePhone=function(){return/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(navigator.userAgent.substr(0,4))},t}();const U=z,K=(t,e,{ignoreKeyboard:o=!1,preventDefaultForKeys:n=!0}={})=>{const i=t=>{"click"!==t.type&&n&&t.preventDefault(),e(t)};t.click(i),!0!==o&&I(t,[{key:C.SPACE},{key:C.ENTER}],i)};function G(t,e){return G=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},G(t,e)}var Q=H5P.jQuery,j="h5p-interactive-video-endscreen",Y="h5p-interactive-video-endscreen-submit-button-hidden",_=function(t){return null!=t};const X=function(t){function e(e){var o,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return(o=t.call(this)||this).parent=e,o.l10n=Q.extend({title:"@answered Questions answered",information:"You have answered @answered questions, click below to submit your answers.",informationOnSubmitButtonDisabled:"You have answered @answered questions.",informationNoAnswers:"You have not answered any questions.",informationMustHaveAnswer:"You have to answer at least one question before you can submit your answers.",submitButton:"Submit Answers",submitMessage:"Your answers have been submitted!",tableRowAnswered:"Answered questions",tableRowScore:"Score",answeredScore:"answered",tableRowSummaryWithScore:"You got @score out of @total points for the @question that appeared after @minutes minutes and @seconds seconds.",tableRowSummaryWithoutScore:"You have answered the @question that appeared after @minutes minutes and @seconds seconds."},n.l10n),o.isSubmitButtonEnabled=o.parent.isSubmitButtonEnabled,o.buildDOM(),o}var o,n;n=t,(o=e).prototype=Object.create(n.prototype),o.prototype.constructor=o,G(o,n);var i=e.prototype;return i.buildDOM=function(){var t=this;this.$endscreenIntroductionTitleText=Q("
",{class:"".concat(j,"-introduction-title-text"),id:"".concat(j,"-introduction-title-text")}),this.$closeButton=Q("
",{role:"button",class:"".concat(j,"-close-button"),tabindex:"0","aria-label":this.parent.l10n.close}),K(this.$closeButton,(function(){return t.parent.toggleEndscreen(!1)}));var e=Q("
",{class:"".concat(j,"-introduction-title")}).append([this.$endscreenIntroductionTitleText,this.$closeButton]);this.$endscreenIntroductionText=Q("
",{class:"".concat(j,"-introduction-text"),id:"".concat(j,"-introduction-text")}),this.isSubmitButtonEnabled&&(this.$submitButtonContainer=Q("
",{class:"".concat(j,"-submit-button-container ").concat(Y)}),this.$submitButton=H5P.JoubelUI.createButton({class:"".concat(j,"-submit-button"),html:this.l10n.submitButton,appendTo:this.$submitButtonContainer,click:function(){return t.handleSubmit()}})),this.$endscreenOverviewTitle=Q("
",{class:"".concat(j,"-overview-title")}).append(Q("
",{class:"".concat(j,"-overview-title-answered-questions"),html:this.l10n.tableRowAnswered})).append(Q("
",{class:"".concat(j,"-overview-title-score"),html:this.l10n.tableRowScore})),this.$endscreenBottomTable=Q("
",{class:"".concat(j,"-overview-table")}),this.$endscreen=Q("
",{class:j,role:"dialog","aria-labelledby":"".concat(j,"-introduction-title-text"),"aria-describedby":"".concat(j,"-introduction-text")}).append(Q("
",{class:"".concat(j,"-introduction")}).append(Q("
",{class:"".concat(j,"-star-symbol")})).append(Q("
",{class:"".concat(j,"-introduction-container")}).append([e,this.$endscreenIntroductionText,this.$submitButtonContainer]))).append(Q("
",{class:"".concat(j,"-overview")}).append(this.$endscreenOverviewTitle).append(this.$endscreenBottomTable))},i.handleSubmit=function(){var t=this;this.$submitButtonContainer.hasClass(Y)||(this.parent.setUserSubmitted(!0),this.$submitButtonContainer.addClass(Y),this.$endscreenIntroductionText.html('")),this.answered.forEach((function(e){if("completed"!==e.getLastXAPIVerb()&&"answered"!==e.getLastXAPIVerb()){var o=new H5P.XAPIEvent;o.data.statement=e.getXAPIData().statement,e.setLastXAPIVerb(o.getVerb()),t.trigger(o)}})),this.parent.triggerXAPIScored(this.parent.getUsersScore(),this.parent.getUsersMaxScore(),"completed"))},i.getDOM=function(){return this.$endscreen},i.buildTableRow=function(t,e,o,n){var i=this,s=_(o)&&_(n),r=s?this.l10n.tableRowSummaryWithScore:this.l10n.tableRowSummaryWithoutScore,a=this.parent.isSkippingProhibited(t)?" ".concat(j,"-no-link"):"",c=Q("
",{class:"".concat(j,"-overview-table-row").concat(a),role:"row",tabIndex:0,"aria-label":r.replace("@score",o).replace("@total",n).replace("@question",e).replace("@minutes",Math.floor(t/60)).replace("@seconds",t%60)});return K(c,(function(){return i.jump(t)})),Q("
",{class:"".concat(j,"-overview-table-row-time"),html:H5P.InteractiveVideo.humanizeTime(t),appendTo:c,"aria-hidden":!0}),Q("
",{class:"".concat(j,"-overview-table-row-title"),html:e,appendTo:c,"aria-hidden":!0}),Q("
",{class:"".concat(j,"-overview-table-row-score"),html:s?"".concat(o," / ").concat(n):this.l10n.answeredScore,appendTo:c,"aria-hidden":!0}),c},i.jump=function(t){this.parent.isSkippingProhibited(t)||(this.parent.seek(t),this.parent.toggleEndscreen(!1))},i.update=function(){var t,e=this,o=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];(this.answered=o.filter((function(t){return void 0!==t.getProgress()})).sort((function(t,e){return t.getDuration().from-e.getDuration().from})),this.isSubmitButtonEnabled)&&(null===(t=this.$submitButtonContainer)||void 0===t||t.addClass(Y));this.$endscreenBottomTable.empty(),this.answered.forEach((function(t){var o=t.getDuration().from,n=e.getDescription(t),i=t.getInstance(),s=i.getScore?i.getScore():void 0,r=i.getMaxScore?i.getMaxScore():void 0;e.$endscreenBottomTable.append(e.buildTableRow(o,n,s,r))}));var n=this.answered.length;this.$endscreenIntroductionTitleText.html(this.l10n.title.replace("@answered",n)),0===n?this.$endscreenIntroductionText.html('
').concat(this.l10n.informationNoAnswers,"
\n ").concat(this.isSubmitButtonEnabled?"
".concat(this.l10n.informationMustHaveAnswer,"
"):"")):this.$endscreenIntroductionText.html(this.isSubmitButtonEnabled?this.l10n.information.replace("@answered",n):this.l10n.informationOnSubmitButtonDisabled.replace("@answered",n)),this.isSubmitButtonEnabled&&n>0&&this.$submitButtonContainer.removeClass(Y)},i.getDescription=function(t){return"function"==typeof t.getInstance&&"function"==typeof t.getInstance().getTitle?t.getInstance().getTitle():t.getTitle()},i.focus=function(){!this.isSubmitButtonEnabled||this.$submitButtonContainer.hasClass(Y)?this.$closeButton.focus():this.$submitButton.focus()},e}(H5P.EventDispatcher);var J=H5P.jQuery;function Z(t,e,o){var n,i,s,r,a,c=this;H5P.EventDispatcher.call(c),c.contentId=e,c.contentData=o,c.params=t,c.instanceIndex=nt(),c.isSubmitButtonEnabled=void 0===o||void 0===o.isScoringEnabled||void 0===o.isReportingEnabled||o.isScoringEnabled||o.isReportingEnabled,c.bookmarksMenuId="interactive-video-"+this.contentId+"-bookmarks-chooser",c.endscreensMenuId="interactive-video-"+this.contentId+"-endscreens-chooser",c.qualityMenuId="interactive-video-"+this.contentId+"-quality-chooser",c.captionsMenuId="interactive-video-"+this.contentId+"-captions-chooser",c.playbackRateMenuId="interactive-video-"+this.contentId+"-playback-rate-chooser",c.popupMenuButtons=[],c.popupMenuChoosers=[],this.visibleInteractions=[],c.isMinimal=!1,c.options=J.extend({video:{textTracks:{videoTrack:[]}},assets:{}},t.interactiveVideo),c.options.video.startScreenOptions=c.options.video.startScreenOptions||{},c.qualities=void 0,c.options.video.startScreenOptions.title||(c.options.video.startScreenOptions.title="Interactive Video"),c.startScreenOptions=J.extend({hideStartTitle:!1,shortStartDescription:""},c.options.video.startScreenOptions),t.override&&(t.override.showSolutionButton||t.override.retryButton)&&(c.override={},t.override.showSolutionButton&&(c.override.enableSolutionsButton="on"===t.override.showSolutionButton),t.override.retryButton&&(c.override.enableRetry="on"===t.override.retryButton)),void 0!==t.override&&(c.showRewind10=void 0!==t.override.showRewind10&&t.override.showRewind10,c.showBookmarksmenuOnLoad=void 0!==t.override.showBookmarksmenuOnLoad&&t.override.showBookmarksmenuOnLoad,c.preventSkippingMode=t.override.preventSkippingMode||"none",c.deactivateSound=t.override.deactivateSound||!1),c.l10n=J.extend({interaction:"Interaction",play:"Play",pause:"Pause",mute:"Mute, currently unmuted",unmute:"Unmute, currently muted",quality:"Video quality",captions:"Captions",close:"Close",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen",summary:"Open summary dialog",bookmarks:"Bookmarks",endscreen:"Submit Screen",endscreens:"Submit Screens",defaultAdaptivitySeekLabel:"Continue",continueWithVideo:"Continue with video",more:"More player options",playbackRate:"Playback rate",rewind10:"Rewind 10 seconds",navDisabled:"Navigation is disabled",navForwardDisabled:"Navigating forward is disabled",sndDisabled:"Sound is disabled",requiresCompletionWarning:"You need to answer all the questions correctly before continuing.",back:"Back",hours:"Hours",minutes:"Minutes",seconds:"Seconds",currentTime:"Current time:",totalTime:"Total time:",singleInteractionAnnouncement:"Interaction appeared:",multipleInteractionsAnnouncement:"Multiple interactions appeared:",videoPausedAnnouncement:"Video was paused",content:"Content",answered:"@answered answered!",videoProgressBar:"Video progress",howToCreateInteractions:"Play the video to start creating interactions"},t.l10n),c.l10n.play+=" (k)",c.l10n.pause+=" (k)",c.l10n.mute+=" (m)",c.l10n.unmute+=" (m)",o&&void 0!==o.previousState&&void 0!==o.previousState.progress&&void 0!==o.previousState.answers&&(c.previousState=o.previousState),c.menuitems=[],c.lastState=H5P.Video.ENDED,c.justVideo=!1;var l=navigator.userAgent.match(/(iPhone|iPod) OS (\d*)_/i);null!==l&&3===l.length&&(c.justVideo=l[2]<10),c.currentTime=Math.floor(void 0!==(null===(n=c.previousState)||void 0===n?void 0:n.progress)&&null!==(null===(i=c.previousState)||void 0===i?void 0:i.progress)?c.previousState.progress:(null===(s=t.override)||void 0===s?void 0:s.startVideoAt)||0),this.maxTimeReached=c.previousState&&c.previousState.maxTimeReached?c.previousState.maxTimeReached:0,c.interactionsProgress=[],c.previousState&&c.previousState.interactionsProgress&&(c.interactionsProgress=c.previousState.interactionsProgress),this.setUserSubmitted(null===(r=this.previousState)||void 0===r?void 0:r.submitted),a=t.override&&!!t.override.loop,c.$videoWrapper=J("
",{class:"h5p-video-wrapper"}),c.$controls=J("
",{role:"toolbar",class:"h5p-controls hidden"}),c.$read=J("
",{"aria-live":"polite",class:"hidden-but-read"}),this.fontSize=16,this.width=640;var d=!1,u=!0,h=!1;if(c.isTask=!1,c.interactions=[],c.options.assets.interactions)for(var p=0;p0&&c.toggleEndscreen(!0),a){c.video.play();var r=t.override&&t.override.startVideoAt?t.override.startVideoAt:0;c.seek(r)}break;case H5P.Video.PLAYING:if(u){c.addQualityChooser(),c.addPlaybackRateChooser(),c.removeSplash(),c.startUpdatingBufferBar(),c.toggleBookmarksChooser(!1,{firstPlay:u}),c.toggleEndscreensChooser(!1,{firstPlay:u}),u=!1;var l=c.options.video.startScreenOptions.poster;l&&void 0!==l.path&&setTimeout((function(){c.trigger("resize")}),400)}c.currentState=H5P.Video.PLAYING,null===(o=c.controls)||void 0===o||o.$play.removeClass("h5p-pause").attr("aria-label",c.l10n.pause),null!==(n=c.controls)&&void 0!==n&&n.$play.is(":focus")&&(c.controls.$play.blur(),c.controls.$play.focus()),c.timeUpdate(c.video.getCurrentTime());break;case H5P.Video.PAUSED:c.currentState=H5P.Video.PAUSED,c.controls.$play.addClass("h5p-pause").attr("aria-label",c.l10n.play),c.focusInteraction?(c.focusInteraction.focusOnFirstTabbableElement(),delete c.focusInteraction):c.controls.$play.is(":focus")&&(c.controls.$play.blur(),c.controls.$play.focus()),c.timeUpdate(c.video.getCurrentTime());break;case H5P.Video.BUFFERING:c.currentState=H5P.Video.BUFFERING,c.removeSplash(),c.startUpdatingBufferBar()}})),c.video.on("qualityChange",(function(t){var e=t.data;if(c.controls&&c.controls.$qualityChooser){if("YouTube"===this.getHandlerName()){if(!c.qualities)return;var o=c.qualities.filter((function(e){return e.name===t.data}))[0];return void c.controls.$qualityChooser.find("li").attr("data-quality",t.data).html(o.label)}c.controls.$qualityChooser.find("li").attr("aria-checked","false").filter('[data-quality="'+e+'"]').attr("aria-checked","true")}})),c.video.on("playbackRateChange",(function(t){var e=t.data;c.controls&&c.controls.$playbackRateChooser&&c.controls.$playbackRateChooser.find("li").attr("aria-checked","false").filter('[playback-rate="'+e+'"]').attr("aria-checked","true")})),c.on("enterFullScreen",(function(){var t=this;c.hasFullScreen=!0,c.$container.parent(".h5p-content").css("height","100%"),c.controls.$fullscreen.addClass("h5p-exit").attr("aria-label",c.l10n.exitFullscreen),c.controls.$fullscreen.blur(),c.controls.$fullscreen.focus(),c.resizeInteractions(),setTimeout((function(){void 0!==t.bubbleEndscreen&&t.bubbleEndscreen.update()}),225)})),c.on("exitFullScreen",(function(){c.$container.hasClass("h5p-standalone")&&c.$container.hasClass("h5p-minimal")&&c.pause(),c.hasFullScreen=!1,c.$container.parent(".h5p-content").css("height",""),c.controls.$fullscreen.removeClass("h5p-exit").attr("aria-label",c.l10n.fullscreen),c.controls.$fullscreen.blur(),c.controls.$fullscreen.focus(),c.resizeInteractions(),c.dnb&&c.dnb.dialog&&!c.hasUncompletedRequiredInteractions()&&c.dnb.dialog.close()})),c.video.on("captions",(function(t){c.controls||(c.addControls(),c.trigger("resize")),c.setCaptionTracks(t.data)})),c.accessibility=new W(c.l10n))}},c.togglePlayPause=function(){var t=c.isDisabled(c.controls.$play);if(c.controls.$play.hasClass("h5p-pause")&&!t){var e=!screen||Math.min(screen.width,screen.height)<=c.width;H5P.fullscreenSupported&&!c.hasFullScreen&&e&&c.$container.hasClass("h5p-standalone")&&c.$container.hasClass("h5p-minimal")&&c.toggleFullScreen(),c.video.play(),c.toggleEndscreen(!1),c.closePopupMenus()}else c.video.pause();c.handleAnswered()},c.toggleMute=function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=c.controls.$volume;c.deactivateSound||(e.hasClass("h5p-muted")?(e.removeClass("h5p-muted").attr("aria-label",c.l10n.mute),c.video.unMute()):(e.addClass("h5p-muted").attr("aria-label",c.l10n.unmute),c.video.mute()),t&&(e.blur(),e.focus()))}}Z.prototype=Object.create(H5P.EventDispatcher.prototype),Z.prototype.constructor=Z,Z.prototype.setCaptionTracks=function(t){var e=this;if(t.unshift(new H5P.Video.LabelValue("Off","off")),e.captionsTrackSelector)e.captionsTrackSelector.updateOptions(t);else{var o=this.editor?void 0:e.options.video.textTracks.defaultTrackLabel,n=t.reduce((function(t,e){return void 0===t&&o&&e.label===o?e:t}),void 0)||e.video.getCaptionsTrack();n||(n=t[0]),e.captionsTrackSelector=new T("captions",t,n,"menuitemradio",e.l10n,e.contentId),e.controls.$captionsButton=J(e.captionsTrackSelector.control),e.popupMenuButtons.push(e.controls.$captionsButton),e.controls.$volume?J(e.captionsTrackSelector.control).insertAfter(e.controls.$volume):J(e.captionsTrackSelector.control).insertAfter(e.controls.$qualityButton),J(e.captionsTrackSelector.popup).css(e.controlsCss).insertAfter(J(e.captionsTrackSelector.control)),e.popupMenuChoosers.push(J(e.captionsTrackSelector.popup)),J(e.captionsTrackSelector.overlayControl).insertAfter(e.controls.$qualityButtonMinimal),e.controls.$overlayButtons=e.controls.$overlayButtons.add(e.captionsTrackSelector.overlayControl),e.captionsTrackSelector.on("select",(function(t){e.video.setCaptionsTrack("off"===t.data.value?null:t.data)})),e.captionsTrackSelector.on("close",(function(){e.controls.$overlayButtons.removeClass("h5p-hide"),"true"===e.controls.$more.attr("aria-expanded")&&e.controls.$more.click(),e.resumeVideo()})),e.captionsTrackSelector.on("open",(function(){e.controls.$overlayButtons.addClass("h5p-hide"),e.closePopupMenus(e.controls.$captionsButton)})),e.minimalMenuKeyboardControls.insertElementAt(e.captionsTrackSelector.overlayControl,2)}},Z.prototype.getCurrentState=function(){var t=this;if(t.video&&t.video.play){var e={submitted:this.userSubmitted,progress:t.currentTime,maxTimeReached:this.maxTimeReached||null,answers:[],interactionsProgress:t.interactions.slice().sort((function(t,e){return t.getDuration().from-e.getDuration().from})).map((function(t){return t.getProgress()}))};if(void 0!==t.interactions)for(var o=0;o
').appendTo(t))},Z.prototype.addSplash=function(){var t=this;void 0!==this.editor||this.video.pressToPlay||!this.video.play||this.$splash||(this.$splash=J('
'+this.options.video.startScreenOptions.title+'
").click((function(){t.video.play()})).appendTo(this.$overlay).find(".h5p-interaction-button").click((function(){return!1})).end(),J(".h5p-splash",this.$splash).keydown((function(e){P(e)&&(t.video.play(),e.preventDefault(),t.$controls.find(".h5p-play").focus())})),void 0!==this.startScreenOptions.shortStartDescription&&this.startScreenOptions.shortStartDescription.length||this.$splash.addClass("no-description"),this.startScreenOptions.hideStartTitle&&this.$splash.addClass("no-title"))},Z.prototype.getDuration=function(){return void 0===this.duration&&(this.duration=this.video.getDuration()),this.duration},Z.prototype.addControls=function(){var t=this;this.addSplash(),this.attachControls(this.$controls.removeClass("hidden"));var e=this.getDuration(),o=Z.humanizeTime(e),n=Z.formatTimeForA11y(e,this.l10n);if(this.controls.$totalTime.find(".human-time").html(o),this.controls.$totalTime.find(".hidden-but-read").html("".concat(this.l10n.totalTime," ").concat(n)),this.controls.$slider.slider("option","max",e),this.bookmarkMenuKeyboardControls=new b([new g]),this.bookmarkMenuKeyboardControls.on("close",(function(){return t.toggleBookmarksChooser(!1)})),this.endscreenMenuKeyboardControls=new b([new g]),this.endscreenMenuKeyboardControls.on("close",(function(){return t.toggleEndscreensChooser(!1)})),this.addSliderInteractions(),this.addBookmarks(),this.options.assets.endscreens&&this.options.assets.endscreens.length>0)for(var i=!1,s=this.getDuration(),r=this.options.assets.endscreens.length-1;r>=0;r--){this.options.assets.endscreens[r].time>s&&(i?(this.options.assets.endscreens.splice(r,1),this.trigger("endscreensChanged",{index:r,number:-1})):(this.options.assets.endscreens[r].time=s,this.options.assets.endscreens[r].label=Z.humanizeTime(s)+" "+this.l10n.endscreen,this.trigger("endscreensChanged",{index:r,number:1}),i=!0))}this.addEndscreenMarkers(),this.addBubbles(),this.trigger("controls")},Z.prototype.loaded=function(){var t=this.getDuration();if(this.oneSecondInPercentage=100/t,t=Math.floor(t),void 0!==this.editor){var e=et("interactions",this.editor.field.fields),o=et("duration",e.field.fields).fields;o[0].max=o[1].max=t,o[0].min=o[1].min=0;for(var n=et("adaptivity",e.field.fields).fields,i=0;i0)for(i=this.options.assets.interactions.length-1;i>=0;i--)if(this.options.assets.interactions[i].duration.to>t){var s=this.options.assets.interactions[i].duration.to-this.options.assets.interactions[i].duration.from,r=t-s<=0?0:t-s;this.options.assets.interactions[i].duration.from=r,this.options.assets.interactions[i].duration.to=t}if(this.hasMainSummary()){var a=t-this.options.summary.displayAt;a<0&&(a=0),void 0===this.options.assets.interactions&&(this.options.assets.interactions=[]),this.options.assets.interactions.push({action:this.options.summary.task,x:80,y:80,duration:{from:a,to:t},displayType:"button",bigDialog:!0,className:"h5p-summary-interaction h5p-end-summary",label:"

"+this.l10n.summary+"

",mainSummary:!0}),this.initInteraction(this.options.assets.interactions.length-1)}this.currentState===Z.ATTACHED&&(this.video.pressToPlay||this.addControls(),this.trigger("resize")),this.currentState=Z.LOADED},Z.prototype.initInteraction=function(t){var e,o=this,n=o.options.assets.interactions[t];if(o.override){var i={};n.adaptivity&&n.adaptivity.requireCompletion&&(i.enableRetry=!0),H5P.jQuery.extend(n.action.params.behaviour,o.override,i)}void 0!==o.previousState&&void 0!==o.previousState.answers&&null!==o.previousState.answers[t]&&(e=o.previousState.answers[t]);var s=new L(n,o,e);return s.on("display",(function(t){var e=t.data;e.appendTo(o.$overlay),s.repositionToWrapper(o.$videoWrapper);var n=void 0!==o.video.pressToPlay;ot(n?100:null,(function(){(o.currentState===H5P.Video.PLAYING||o.currentState===H5P.Video.BUFFERING)&&s.pause()&&(o.focusInteraction||(o.focusInteraction=s),o.video.pause())})),o.seekingTo&&(o.seekingTo=void 0,e.focus()),setTimeout((function(){s.positionLabel(o.$videoWrapper.width())}),0),o.toggleEndscreen(!1)})),s.on("hide",(function(){o.handleAnswered()})),s.on("xAPI",(function(t){var e=t.getVerb();"interacted"===e&&this.setProgress(L.PROGRESS_INTERACTED),-1!==J.inArray(e,["completed","answered"])&&t.setVerb("answered"),void 0===t.data.statement.context.extensions&&(t.data.statement.context.extensions={}),t.data.statement.context.extensions["http://id.tincanapi.com/extension/ending-point"]="PT"+Math.floor(o.video.getCurrentTime())+"S"})),o.interactions.push(s),s},Z.prototype.handleAnswered=function(){var t=this;t.interactions.forEach((function(e){e.getProgress()===L.PROGRESS_INTERACTED&&(e.setProgress(L.PROGRESS_ANSWERED),e.$menuitem.addClass("h5p-interaction-answered"),t.hasStar&&(t.playStarAnimation(),t.playBubbleAnimation(t.l10n.answered.replace("@answered",""+t.getAnsweredTotal()+"")),t.endscreen.update(t.interactions)))}))},Z.prototype.getAnsweredTotal=function(){return this.interactions.filter((function(t){return t.getProgress()===L.PROGRESS_ANSWERED})).length},Z.prototype.hasMainSummary=function(){var t=this.options.summary;return!(void 0===t||void 0===t.displayAt||void 0===t.task||void 0===t.task.params||void 0===t.task.params.summaries||!t.task.params.summaries.length||void 0===t.task.params.summaries[0].summary||!t.task.params.summaries[0].summary.length)},Z.prototype.addSliderInteractions=function(){var t=this,e=this;this.controls.$interactionsContainer.children().remove(),this.interactionKeyboardControls.elements=[],H5P.jQuery.extend([],this.interactions).sort((function(t,e){return t.getDuration().from-e.getDuration().from})).forEach((function(o){var n=o.addDot();e.menuitems.push(n),void 0===e.previousState&&e.interactionsProgress.push(void 0),e.interactionsProgress[e.menuitems.length-1]===L.PROGRESS_ANSWERED&&(o.setProgress(e.interactionsProgress[e.menuitems.length-1]),n.addClass("h5p-interaction-answered")),void 0!==n&&(n.appendTo(t.controls.$interactionsContainer),"both"!==e.preventSkippingMode&&t.interactionKeyboardControls.addElement(n.get(0)))})),e.interactionKeyboardControls.on("afterNextElement",(function(e){return t.handleInteractionTabIndex(e)})),e.interactionKeyboardControls.on("afterPreviousElement",(function(e){return t.handleInteractionTabIndex(e)}))},Z.prototype.handleInteractionTabIndex=function(t){t.element.removeAttribute("tabindex")},Z.prototype.closePopupMenus=function(t){this.popupMenuButtons.forEach((function(e){void 0!==e&&e!==t&&void 0===e.attr("aria-disabled")&&"true"===e.attr("aria-expanded")&&e.click()}))},Z.prototype.displayBookmarks=function(){return this.options.assets.bookmarks&&this.options.assets.bookmarks.length&&"both"!==this.preventSkippingMode},Z.prototype.addBookmarks=function(){if(this.bookmarksMap={},void 0!==this.options.assets.bookmarks&&"both"!==this.preventSkippingMode)for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:{initialLoad:!1,keepStopped:!1,firstPlay:!1};if(null!==(e=this.controls)&&void 0!==e&&e.$bookmarksButton){t=void 0===t?!this.controls.$bookmarksChooser.hasClass("h5p-show"):t;var i=this.controls.$bookmarksChooser.hasClass("h5p-show");this.controls.$minimalOverlay.toggleClass("h5p-show",t),this.controls.$minimalOverlay.find(".h5p-minimal-button").toggleClass("h5p-hide",t),this.controls.$bookmarksButton.attr("aria-expanded",!!t&&"true"),this.controls.$more.attr("aria-expanded",t?"true":"false"),this.controls.$bookmarksChooser.css({maxHeight:t?this.controlsCss.maxHeight:"32px"}).toggleClass("h5p-show",t).toggleClass("h5p-transitioning",t||i)}t?(this.closePopupMenus(this.controls.$bookmarksButton),this.showBookmarksmenuOnLoad&&n.initialLoad||this.controls.$bookmarksChooser[0].addEventListener("transitionend",(function(){o.controls.$bookmarksChooser.find('[tabindex="0"]').first().focus()}),{once:!0}),this.editor&&(this.interruptVideo(),this.updateChooserTime(this.controls.$bookmarksChooser,".h5p-add-bookmark"))):n.firstPlay||(this.editor&&!n.keepStopped&&this.resumeVideo(),this.controls.$bookmarksChooser.hasClass("h5p-show")||this.controls.$bookmarksButton.focus())},Z.prototype.toggleEndscreensChooser=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{keepStopped:!1,firstPlay:!1};if(this.editor&&this.controls.$endscreensButton){t=void 0===t?!this.controls.$endscreensChooser.hasClass("h5p-show"):t;var o=this.controls.$endscreensChooser.hasClass("h5p-show");this.controls.$minimalOverlay.toggleClass("h5p-show",t),this.controls.$minimalOverlay.find(".h5p-minimal-button").toggleClass("h5p-hide",t),this.controls.$endscreensButton.attr("aria-expanded",t?"true":"false").toggleClass("h5p-star-active-editor",t),this.controls.$more.attr("aria-expanded",t?"true":"false");var n=-10+Math.min(0,this.$container.outerWidth()-this.controls.$endscreensChooser.parent().offset().left-this.controls.$endscreensChooser.outerWidth())+"px";this.controls.$endscreensChooser.css({maxHeight:t?this.controlsCss.maxHeight:"32px"}).css({left:n}).toggleClass("h5p-show",t).toggleClass("h5p-transitioning",t||o)}t?(this.closePopupMenus(this.controls.$endscreensButton),this.editor&&(this.interruptVideo(),this.updateChooserTime(this.controls.$endscreensChooser,".h5p-add-endscreen")),this.controls.$endscreensChooser.find('[tabindex="0"]').first().focus()):e.firstPlay||(this.editor&&!e.keepStopped&&this.resumeVideo(),this.controls.$endscreensChooser.hasClass("h5p-show")||this.controls.$endscreensButton.focus())},Z.prototype.updateChooserTime=function(t,e){var o=t.find(e);o.html(o.data("default").replace("@timecode",Z.humanizeTime(this.video.getCurrentTime())))},Z.prototype.interruptVideo=function(){this.currentState===H5P.Video.PLAYING&&(this.interruptedTemporarily=!0,this.video.pause())},Z.prototype.resumeVideo=function(t){if(!t){if(!this.interruptedTemporarily)return;if(this.popupMenuChoosers.some((function(t){return t.hasClass("h5p-show")})))return}this.interruptedTemporarily=!1,this.video.play()},Z.prototype.toggleEndscreen=function(t){var e,o;!this.editor&&this.hasStar&&t!==(null===(e=this.bubbleEndscreen)||void 0===e?void 0:e.isActive())&&null!==(o=this.controls)&&void 0!==o&&o.$endscreensButton&&((t=void 0===t?!this.bubbleEndscreen.isActive():t)?(this.disableTabIndexes(".h5p-interactive-video-endscreen"),this.stateBeforeEndscreen=this.currentState,this.video.pause(),this.endscreen.update(this.interactions)):(this.restoreTabIndexes(),this.controls.$endscreensButton.focus(),this.stateBeforeEndscreen===H5P.Video.PLAYING&&(this.video.play(),this.stateBeforeEndscreen=void 0)),this.controls.$endscreensButton.attr("aria-expanded",t),this.controls.$endscreensButton.toggleClass("h5p-star-active",t),this.bubbleEndscreen.toggle(t,!0))},Z.prototype.showPreventSkippingMessage=function(){var t,e,o,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=arguments.length>1?arguments[1]:void 0,s=this;if(n.x=null!==(t=n.x)&&void 0!==t?t:0,n.y="number"==typeof n.y?25-n.y:25,i=null!==(e=i)&&void 0!==e?e:s.l10n.navDisabled,!s.preventSkippingWarningTimeout){s.$preventSkippingMessage||(s.$preventSkippingMessage=J("
",{class:"h5p-prevent-skipping-message",appendTo:s.controls.$bookmarksContainer}),s.$preventSkippingMessage=J("
",{class:"h5p-prevent-skipping-message",appendTo:s.controls.$endscreensContainer}),s.$preventSkippingMessageText=J("
",{class:"h5p-prevent-skipping-message-text",html:i,appendTo:s.$preventSkippingMessage}),s.$preventSkippingMessageTextA11y=J("
",{class:"hidden-but-read",html:i,appendTo:s.controls.$slider}));var r=s.$preventSkippingMessage.innerWidth()-s.$preventSkippingMessage.width();s.$preventSkippingMessage.css("max-width","".concat(s.$videoWrapper.width()-r,"px"));var a=parseFloat(null!==(o=s.$preventSkippingMessage.css("transform").split(",")[4])&&void 0!==o?o:0),c=s.$sliderContainer.get(0).offsetLeft+s.controls.$slider.get(0).offsetLeft+n.x+a+s.$preventSkippingMessage.outerWidth(),l=Math.max(0,c-s.$container.width());n.x-=l,s.$preventSkippingMessage.toggleClass("h5p-overflow",l>0),s.$preventSkippingMessage.css("left",n.x),s.$preventSkippingMessage.css("bottom",n.y),setTimeout((function(){s.$preventSkippingMessage.addClass("h5p-show").attr("aria-hidden","false")}),0),s.preventSkippingWarningTimeout=setTimeout((function(){s.$preventSkippingMessage.removeClass("h5p-show").attr("aria-hidden","true"),setTimeout((function(){s.preventSkippingWarningTimeout=void 0}),500)}),2e3)}},Z.prototype.onBookmarkSelect=function(t,e){var o=this;if(this.isSkippingProhibited(e.time))this.showPreventSkippingMessage({x:e.time/o.video.getDuration()*o.controls.$slider.get(0).offsetWidth+2,y:-23},o.l10n.navForwardDisabled);else{o.currentState!==H5P.Video.PLAYING&&(t.mouseover().mouseout(),setTimeout((function(){o.timeUpdate(o.video.getCurrentTime())}),0)),"true"===o.controls.$more.attr("aria-expanded")&&o.$container.hasClass("h5p-minimal")?o.controls.$more.click():o.toggleBookmarksChooser(!1),o.video.play(),o.seek(e.time);var n=Z.formatTimeForA11y(e.time,o.l10n);setTimeout((function(){return o.read("".concat(o.l10n.currentTime," ").concat(n))}),150)}},Z.prototype.onEndscreenSelect=function(t,e){var o=this;o.currentState!==H5P.Video.PLAYING&&(t.mouseover().mouseout(),setTimeout((function(){o.timeUpdate(o.video.getCurrentTime())}),0)),"true"===o.controls.$more.attr("aria-expanded")&&o.$container.hasClass("h5p-minimal")?o.controls.$more.click():o.toggleEndscreensChooser(!1),o.video.play(),o.seek(e.time);var n=Z.formatTimeForA11y(e.time,o.l10n);setTimeout((function(){return o.read("".concat(o.l10n.currentTime," ").concat(n))}),150)},Z.prototype.addBookmark=function(t,e){var o=this,n=o.options.assets.bookmarks[t];void 0===e&&(e=Math.floor(10*n.time)/10);var i=o.bookmarksMap[e]=J('
'+n.label+"
").appendTo(o.controls.$bookmarksContainer).data("id",t).hover((function(){void 0!==o.bookmarkTimeout&&clearTimeout(o.bookmarkTimeout),o.controls.$bookmarksContainer.children(".h5p-show").removeClass("h5p-show"),i.addClass("h5p-show")}),(function(){o.bookmarkTimeout=setTimeout((function(){i.removeClass("h5p-show")}),o.editor?1e3:2e3)}));i.find(".h5p-bookmark-label").css("maxWidth",parseInt(o.controls.$slider.parent().css("marginRight"))-35),void 0===o.controls.$bookmarksList&&(o.controls.$bookmarksList=J('
    ').insertAfter(o.controls.$bookmarksChooser.find("h2")));var s=J('
  • ').concat(n.label,"
  • ")).click((function(){return o.onBookmarkSelect(i,n)})).keydown((function(t){P(t)&&o.onBookmarkSelect(i,n),t.stopPropagation()}));o.bookmarkMenuKeyboardControls.addElement(s.get(0));var r=o.controls.$bookmarksList.children(":eq("+t+")");return 0!==r.length?s.insertBefore(r):s.appendTo(o.controls.$bookmarksList),o.on("bookmarksChanged",(function(n){var r=n.data.index,a=n.data.number;r===t&&a<0?(s.remove(),delete o.bookmarksMap[e]):t>=r&&(t+=a,i.data("id",t))})),o.trigger("bookmarkAdded",{bookmark:i}),i},Z.prototype.addEndscreen=function(t,e){var o,n=this,i=n.options.assets.endscreens[t];if(void 0===e&&(e=Math.floor(10*i.time)/10),!this.editor)return n.getDuration()-e<1&&(e=n.getDuration()),void(o=n.endscreensMap[e]=!0);o=n.endscreensMap[e]=J('
    '+i.label+"
    ").appendTo(n.controls.$endscreensContainer).data("id",t).hover((function(){void 0!==n.endscreenTimeout&&clearTimeout(n.endscreenTimeout),n.controls.$endscreensContainer.children(".h5p-show").removeClass("h5p-show"),o.addClass("h5p-show")}),(function(){n.endscreenTimeout=setTimeout((function(){o.removeClass("h5p-show")}),n.editor?1e3:2e3)})),n.editor&&(n.endscreenTimeout=setTimeout((function(){o.removeClass("h5p-show")}),1e3)),o.find(".h5p-endscreen-label").css("maxWidth",parseInt(n.controls.$slider.parent().css("marginRight"))-35),void 0===n.controls.$endscreensList&&(n.controls.$endscreensList=J('
      ').insertAfter(n.controls.$endscreensChooser.find("h2")));var s=J('
    • ').concat(i.label,"
    • ")).click((function(){return n.onEndscreenSelect(o,i)})).keydown((function(t){P(t)&&n.onEndscreenSelect(o,i),t.stopPropagation()}));n.endscreenMenuKeyboardControls.addElement(s.get(0));var r=n.controls.$endscreensList.children(":eq("+t+")");return 0!==r.length?s.insertBefore(r):s.appendTo(n.controls.$endscreensList),n.on("endscreensChanged",(function(i){var r=i.data.index,a=i.data.number;r===t&&a<0?(s.remove(),delete n.endscreensMap[e]):t>=r&&(t+=a,o.data("id",t))})),n.trigger("endscreenAdded",{endscreen:o}),o},Z.prototype.attachControls=function(t){var e=this,o=J("
      ",{class:"h5p-controls-left",appendTo:t});e.$sliderContainer=J("
      ",{class:"h5p-control h5p-slider",appendTo:t}),e.hasStar&&(e.$star=J("
      ",{class:"h5p-control h5p-star",appendTo:t}),e.$starBar=J("
      ",{class:"h5p-control h5p-star h5p-star-bar",appendTo:e.$star}),J("
      ",{class:"h5p-control h5p-star h5p-star-background",appendTo:e.$star}),e.$starAnimation=J("
      ",{class:"h5p-control h5p-star h5p-star-animation h5p-star-animation-inactive",appendTo:e.$star}));var n=J("
      ",{class:"h5p-controls-right",appendTo:t});"both"===e.preventSkippingMode&&e.setDisabled(e.$sliderContainer),e.controls={},e.controls.$play=e.createButton("play","h5p-control h5p-pause",o,e.togglePlayPause),e.showRewind10&&(e.controls.$rewind10=e.createButton("rewind10","h5p-control",o,(function(){if(e.video.getCurrentTime()>0){var t=Math.max(e.video.getCurrentTime()-10,0);e.seek(t),e.currentState===H5P.Video.PAUSED&&e.timeUpdate(t),e.currentState===H5P.Video.ENDED&&e.video.play()}})));var i=function(t,o){return function(){var n=e.controls[t],i=e.controls[o],s="true"===n.attr("aria-disabled"),r="true"===n.attr("aria-expanded");s||(r?(n.attr("aria-expanded","false"),i.hasClass("h5p-show")||n.focus(),i.removeClass("h5p-show"),function(){var t=e.$container.hasClass("h5p-minimal")&&"true"===e.controls.$more.attr("aria-expanded");t&&e.controls.$more.click()}(),e.resumeVideo()):(n.attr("aria-expanded","true"),i.addClass("h5p-show"),i.find('[tabindex="0"]').focus(),e.closePopupMenus(n)))}},s=e.editor||e.displayBookmarks();if(s&&(e.controls.$bookmarksChooser=H5P.jQuery("
      ",{class:"h5p-chooser h5p-bookmarks",role:"dialog",html:'

      ').concat(e.l10n.bookmarks,"

      ")}),e.popupMenuChoosers.push(e.controls.$bookmarksChooser),e.controls.$bookmarksChooser.append(J("",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return e.toggleBookmarksChooser()},keydown:function(t){P(t)&&(e.toggleBookmarksChooser(),t.preventDefault())}})),e.showRewind10&&e.controls.$bookmarksChooser.addClass("h5p-rewind-displacement"),e.controls.$bookmarksButton=e.createButton("bookmarks","h5p-control",o,(function(){e.toggleBookmarksChooser()})),e.controls.$bookmarksButton.attr("aria-haspopup","true"),e.controls.$bookmarksButton.attr("aria-expanded","false"),e.controls.$bookmarksChooser.insertAfter(e.controls.$bookmarksButton),e.controls.$bookmarksChooser.bind("transitionend",(function(){e.controls.$bookmarksChooser.removeClass("h5p-transitioning")})),e.popupMenuButtons.push(e.controls.$bookmarksButton)),e.hasStar){var r=e.editor?"star h5p-star-foreground-editor":"star h5p-star-foreground",a=e.editor?function(){return e.toggleEndscreensChooser()}:function(){return e.toggleEndscreen()};e.controls.$endscreensButton=e.createButton(r,"h5p-control",e.$star,a),e.controls.$endscreensButton.attr("aria-label",e.l10n.summary).attr("aria-haspopup","dialog").attr("aria-expanded","false"),e.popupMenuButtons.push(e.controls.$endscreensButton)}e.editor&&(e.controls.$endscreensChooser=H5P.jQuery("
      ",{class:"h5p-chooser h5p-endscreens",role:"dialog",html:'

      ').concat(e.l10n.endscreens,"

      ")}),e.popupMenuChoosers.push(e.controls.$endscreensChooser),e.controls.$endscreensChooser.append(J("",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return e.toggleEndscreensChooser()},keydown:function(t){P(t)&&(e.toggleEndscreensChooser(),t.preventDefault())}})),e.hasStar&&(e.controls.$endscreensButton.attr("aria-haspopup","true").attr("aria-expanded","false"),e.controls.$endscreensChooser.insertAfter(e.controls.$endscreensButton).bind("transitionend",(function(){e.controls.$endscreensChooser.removeClass("h5p-transitioning")}))));var c='',l=J('
      '.concat(c,"
      ")).appendTo(o);e.controls.$currentTimeSimple=l.find(".human-time"),e.controls.$currentTimeA11ySimple=l.find(".hidden-but-read");var d=Z.formatTimeForA11y(0,e.l10n),u=J('
      \n '.concat(c,'\n \n \n
      ')).appendTo(n),h=u.find(".h5p-current");e.controls.$currentTime=h.find(".human-time"),e.controls.$currentTimeA11y=h.find(".hidden-but-read"),e.controls.$totalTime=u.find(".h5p-total"),e.updateCurrentTime(0);var p=function(){e.controls.$minimalOverlay.removeClass("h5p-show"),e.controls.$more.attr("aria-expanded","false"),e.controls.$more.focus(),setTimeout((function(){e.controls.$overlayButtons.removeClass("h5p-hide")}),150)};e.controls.$more=e.createButton("more","h5p-control",n,(function(){"true"===e.controls.$more.attr("aria-expanded")?p():(e.controls.$minimalOverlay.addClass("h5p-show"),e.controls.$more.attr("aria-expanded","true"),e.removeSplash(),setTimeout((function(){e.controls.$minimalOverlay.find('[tabindex="0"]').focus()}),150)),e.closePopupMenus()})),e.controls.$playbackRateChooser=H5P.jQuery("
      ",{class:"h5p-chooser h5p-playbackRate",role:"dialog",html:'

      ').concat(e.l10n.playbackRate,"

      ")}),e.popupMenuChoosers.push(e.controls.$playbackRateChooser);var m=function(){e.isMinimal?e.controls.$more.click():e.controls.$playbackRateButton.click(),e.resumeVideo()};e.controls.$playbackRateChooser.append(J("",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return m()},keydown:function(t){P(t)&&(m(),t.preventDefault())}})),e.controls.$playbackRateButton=e.createButton("playbackRate","h5p-control",n,i("$playbackRateButton","$playbackRateChooser")),e.popupMenuButtons.push(e.controls.$playbackRateButton),e.setDisabled(e.controls.$playbackRateButton),e.controls.$playbackRateButton.attr("aria-haspopup","true"),e.controls.$playbackRateButton.attr("aria-expanded","false"),e.controls.$playbackRateChooser.insertAfter(e.controls.$playbackRateButton),-1===navigator.userAgent.indexOf("Android")&&-1===navigator.userAgent.indexOf("iPad")&&(e.controls.$volume=e.createButton("mute","h5p-control",n,e.toggleMute),e.deactivateSound&&(e.controls.$volume.addClass("h5p-muted").attr("aria-label",e.l10n.sndDisabled),e.setDisabled(e.controls.$volume))),e.deactivateSound&&e.video.mute(),e.video.isMuted()&&e.controls.$volume.addClass("h5p-muted").attr("aria-label",e.l10n.sndDisabled),e.controls.$qualityChooser=H5P.jQuery("
      ",{class:"h5p-chooser h5p-quality",role:"dialog",html:'

      ').concat(e.l10n.quality,"

      ")}),e.popupMenuChoosers.push(e.controls.$qualityChooser);var v=function(){e.isMinimal?e.controls.$more.click():e.controls.$qualityButton.click(),e.resumeVideo()};e.controls.$qualityChooser.append(J("",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return v()},keydown:function(t){P(t)&&(v(),t.preventDefault())}})),e.controls.$qualityButton=e.createButton("quality","h5p-control",n,i("$qualityButton","$qualityChooser")),e.popupMenuButtons.push(e.controls.$qualityButton),e.setDisabled(e.controls.$qualityButton),e.controls.$qualityButton.attr("aria-haspopup","true"),e.controls.$qualityButton.attr("aria-expanded","false"),e.controls.$qualityChooser.insertAfter(e.controls.$qualityButton),e.editor||!1===H5P.fullscreenSupported||(e.controls.$fullscreen=e.createButton("fullscreen","h5p-control",n,(function(){e.toggleFullScreen()}))),e.controls.$minimalOverlay=H5P.jQuery("
      ",{class:"h5p-minimal-overlay",appendTo:e.$container});var f=H5P.jQuery("
      ",{role:"menu",class:"h5p-minimal-wrap",appendTo:e.controls.$minimalOverlay});e.minimalMenuKeyboardControls=new b([new g]),e.minimalMenuKeyboardControls.on("close",(function(){return p()})),e.controls.$overlayButtons=H5P.jQuery([]),s&&(e.controls.$bookmarkButtonMinimal=e.createButton("bookmarks","h5p-minimal-button",f,(function(){e.controls.$overlayButtons.addClass("h5p-hide"),e.toggleBookmarksChooser(!0)}),!0),e.controls.$bookmarkButtonMinimal.attr("role","menuitem"),e.controls.$bookmarkButtonMinimal.attr("tabindex","-1"),e.controls.$overlayButtons=e.controls.$overlayButtons.add(e.controls.$bookmarkButtonMinimal),e.minimalMenuKeyboardControls.addElement(e.controls.$bookmarkButtonMinimal.get(0))),e.controls.$qualityButtonMinimal=e.createButton("quality","h5p-minimal-button",f,(function(){e.isDisabled(e.controls.$qualityButton)||(e.controls.$overlayButtons.addClass("h5p-hide"),e.controls.$qualityButton.click())}),!0),e.setDisabled(e.controls.$qualityButtonMinimal),e.controls.$qualityButtonMinimal.attr("role","menuitem"),e.controls.$overlayButtons=e.controls.$overlayButtons.add(e.controls.$qualityButtonMinimal),e.minimalMenuKeyboardControls.addElement(e.controls.$qualityButtonMinimal.get(0)),e.controls.$playbackRateButtonMinimal=e.createButton("playbackRate","h5p-minimal-button",f,(function(){e.isDisabled(e.controls.$playbackRateButton)||(e.controls.$overlayButtons.addClass("h5p-hide"),e.controls.$playbackRateButton.click())}),!0),e.controls.$playbackRateButtonMinimal.attr("role","menuitem"),e.setDisabled(e.controls.$playbackRateButtonMinimal),e.controls.$overlayButtons=e.controls.$overlayButtons.add(e.controls.$playbackRateButtonMinimal),e.minimalMenuKeyboardControls.addElement(e.controls.$playbackRateButtonMinimal.get(0)),e.addQualityChooser(),e.addPlaybackRateChooser(),e.interactionKeyboardControls=new b([new g]),e.controls.$interactionsContainer=J("
      ",{role:"menu",class:"h5p-interactions-container","aria-label":e.l10n.interaction}),e.controls.$bookmarksContainer=J("
      ",{class:"h5p-bookmarks-container",appendTo:e.$sliderContainer}),e.controls.$endscreensContainer=J("
      ",{class:"h5p-endscreens-container",appendTo:e.$sliderContainer}),e.hasPlayPromise=!1,e.hasQueuedPause=!1,e.delayed=!1,e.controls.$slider=J("
      ",{appendTo:e.$sliderContainer}).slider({value:0,step:.01,orientation:"horizontal",range:"min",max:0,create:function(t){var o=J(t.target).find(".ui-slider-handle");o.attr("role","slider").attr("aria-valuemin","0").attr("aria-valuemax",e.getDuration().toString()).attr("aria-valuetext",Z.formatTimeForA11y(0,e.l10n)).attr("aria-valuenow","0").attr("aria-label",e.l10n.videoProgressBar).attr("tabindex","0"),"both"===e.preventSkippingMode&&e.setDisabled(o).attr("aria-hidden","true")},start:function(){e.currentState!==Z.SEEKING&&(e.toggleEndscreen(!1),e.delayedState||(e.currentState===H5P.Video.ENDED?e.lastState=H5P.Video.PLAYING:e.currentState===H5P.Video.BUFFERING&&e.lastState||(e.lastState=e.currentState)),e.delayedState=!0,clearTimeout(e.delayTimeout),e.delayTimeout=setTimeout((function(){e.delayedState=!1}),200),e.hasPlayPromise?e.hasQueuedPause=!0:e.video.pause(),e.currentState=Z.SEEKING,e.removeSplash(),e.$overlay.addClass("h5p-visible"))},slide:function(t,o){var n=x(t,[C.ARROW_LEFT,C.ARROW_RIGHT]),i=o.value;if(e.isSkippingProhibited(i)&&(i=e.maxTimeReached),n){var s=x(t,[C.ARROW_RIGHT]),r=e.getDuration();i=s?Math.min(i+5,r):Math.max(i-5,0),e.isSkippingProhibited(i)&&(i=e.maxTimeReached),e.timeUpdate(i,!0)}return e.seek(i),e.updateInteractions(i),e.updateCurrentTime(i),!n},stop:function(t,o){e.currentState=e.lastState;var n=e.isSkippingProhibited(o.value)?e.maxTimeReached:o.value;e.seek(n),e.recreateCurrentInteractions();var i=e.lastState===H5P.Video.PLAYING||e.lastState===H5P.Video.VIDEO_CUED||e.hasQueuedPause;if(e.hasPlayPromise)e.hasQueuedPause=!1;else if(i){e.hasQueuedPause=!1;var s=e.video.play();e.hasQueuedPause=!1,s&&s.then?(e.hasPlayPromise=!0,s.then((function(){setTimeout((function(){(e.hasQueuedPause||e.hasActivePauseInteraction())&&e.video.pause(),e.hasPlayPromise=!1}),0)}))):e.hasActivePauseInteraction()?(e.video.play(),setTimeout((function(){e.video.pause()}),50)):e.timeUpdate(n)}else e.timeUpdate(n);e.isSkippingProhibited(o.value)&&(e.showPreventSkippingMessage({x:o.value/e.video.getDuration()*e.controls.$slider.get(0).offsetWidth},e.l10n.navForwardDisabled),e.setSliderPosition(n)),e.$overlay.removeClass("h5p-visible"),e.editor&&(e.updateChooserTime(e.controls.$bookmarksChooser,".h5p-add-bookmark"),e.updateChooserTime(e.controls.$endscreensChooser,".h5p-add-endscreen"))}}),e.controls.$interactionsContainer.appendTo(e.$sliderContainer),"both"===e.preventSkippingMode&&(e.controls.$slider.slider("disable"),e.controls.$slider.parent().click((function(t){var o=e.menuitems.map((function(t){return t.get(0)})).includes(t.target)?t.target.offsetLeft:t.offsetX;return e.showPreventSkippingMessage({x:o,y:-13}),!1}))),e.displayBookmarks()&&e.showBookmarksmenuOnLoad&&!1===e.video.pressToPlay&&e.toggleBookmarksChooser(!0,{initialLoad:!0}),e.controls.$buffered=J("
      ",{class:"h5p-buffered",prependTo:e.controls.$slider})},Z.prototype.playStarAnimation=function(){var t=this;this.$starAnimation.hasClass("h5p-star-animation-inactive")&&(this.$starAnimation.removeClass("h5p-star-animation-inactive").addClass("h5p-star-animation-active"),setTimeout((function(){t.$starAnimation.removeClass("h5p-star-animation-active").addClass("h5p-star-animation-inactive")}),1e3))},Z.prototype.playBubbleAnimation=function(t){this.bubbleScore.setContent(t),this.bubbleScore.animate()},Z.prototype.hasActivePauseInteraction=function(){var t=!1;return this.interactions.forEach((function(e){e.getElement()&&e.pause()&&(t=!0)})),t},Z.prototype.createButton=function(t,e,o,n,i){var s={role:"button",tabindex:0,class:(void 0===e?"":e+" ")+"h5p-"+t,on:{click:function(){n.call(this)},keydown:function(t){P(t)&&(n.call(this),t.preventDefault(),t.stopPropagation())}},appendTo:o};return s[i?"text":"aria-label"]=this.l10n[t],H5P.jQuery("
      ",s)},Z.prototype.addQualityChooser=function(){var t,e=this;if(e.qualityMenuKeyboardControls=new b([new g]),e.qualityMenuKeyboardControls.on("close",(function(){return e.controls.$qualityButton.click()})),this.video.getQualities&&(e.qualities=this.video.getQualities(),e.qualities&&void 0!==(null===(t=this.controls)||void 0===t?void 0:t.$qualityButton)&&e.isDisabled(e.controls.$qualityButton))){var o=this.video.getQuality(),n=e.qualities;"YouTube"===this.video.getHandlerName()&&(n=n.filter((function(t){return t.name===o})));for(var i="",s=0;s').concat(r.label,"")}var c=J('
        '.concat(i,"
      ")).appendTo(this.controls.$qualityChooser);c.children().click((function(){var t=J(this).attr("data-quality");e.updateQuality(t)})).keydown((function(t){if(P(t)){var o=J(this).attr("data-quality");e.updateQuality(o)}t.stopPropagation()})),c.find("li").get().forEach((function(t){e.qualityMenuKeyboardControls.addElement(t);var o="true"===t.getAttribute("aria-checked");tt(t,o)})),e.removeDisabled(this.controls.$qualityButton.add(this.controls.$qualityButtonMinimal))}},Z.prototype.updateQuality=function(t){var e=this;e.video.setQuality(t),"true"===e.controls.$more.attr("aria-expanded")?e.controls.$more.click():(e.controls.$qualityButton.click(),e.controls.$qualityButton.focus())},Z.prototype.addPlaybackRateChooser=function(){var t,e=this,o=this;if(this.playbackRateMenuKeyboardControls=new b([new g]),this.playbackRateMenuKeyboardControls.on("close",(function(){return o.controls.$playbackRateButton.click()})),this.video.getPlaybackRates){var n=this.video.getPlaybackRates();if(!(n.length<2)&&n&&void 0!==(null===(t=this.controls)||void 0===t?void 0:t.$playbackRateButton)&&o.isDisabled(this.controls.$playbackRateButton)){for(var i=this.video.getPlaybackRate(),s="",r=0;r').concat(a,"")}var l=J('
        '+s+"
      ").appendTo(this.controls.$playbackRateChooser);l.children().click((function(){var t=J(this).attr("playback-rate");o.updatePlaybackRate(t)})).keydown((function(t){if(P(t)){var e=J(this).attr("playback-rate");o.updatePlaybackRate(e)}t.stopPropagation()})),l.find("li").get().forEach((function(t){e.playbackRateMenuKeyboardControls.addElement(t);var o="true"===t.getAttribute("aria-checked");tt(t,o)})),o.removeDisabled(this.controls.$playbackRateButton.add(this.controls.$playbackRateButtonMinimal))}}},Z.prototype.updatePlaybackRate=function(t){var e=this;e.video.setPlaybackRate(t),"true"===e.controls.$more.attr("aria-expanded")?e.controls.$more.click():e.controls.$playbackRateButton.click()},Z.prototype.startUpdatingBufferBar=function(){var t=this;if(!t.bufferLoop){!function e(){var o=t.video.getBuffered();o&&t.controls.$buffered&&(t.controls.$buffered.css("width",o+"%"),t.hasStar&&(o>99?t.$starBar.addClass("h5p-star-bar-buffered"):t.$starBar.removeClass("h5p-star-bar-buffered"))),t.bufferLoop=setTimeout(e,500)}()}},Z.prototype.resize=function(){if(this.$container){var t,e,o=this.$container.hasClass("h5p-fullscreen")||this.$container.hasClass("h5p-semi-fullscreen");this.$videoWrapper.css({marginTop:"",marginLeft:"",width:"",height:""}),this.video.trigger("resize");var n=this.justVideo?0:this.$controls.height(),i=this.$container.height();if(o){if((e=this.$videoWrapper.height())+n<=i)this.$videoWrapper.css("marginTop",(i-n-e)/2),t=this.$videoWrapper.width(),void 0!==this.bubbleEndscreen&&this.bubbleEndscreen.fullscreen(!0,i,e);else{var s=i-n;t=s*(this.$videoWrapper.width()/e),this.$videoWrapper.css({marginLeft:(this.$container.width()-t)/2,width:t,height:s}),void 0!==this.bubbleEndscreen&&this.bubbleEndscreen.fullscreen(!0)}this.video.trigger("resize")}else t=this.$container.width(),void 0!==this.bubbleEndscreen&&this.bubbleEndscreen.fullscreen();this.scaledFontSize=t>this.width?this.fontSize*(t/this.width):this.fontSize,this.$container.css("fontSize",this.scaledFontSize+"px"),this.editor||(t=0&&(this.maxTimeReached=Math.max(this.maxTimeReached,t),this.setSliderPosition(t)),o.updateInteractions(t),e||(this.timeUpdateTimeout=window.setTimeout((function(){(o.currentState===H5P.Video.PLAYING||o.currentState===H5P.Video.BUFFERING&&o.lastState===H5P.Video.PLAYING)&&o.timeUpdate(o.video.getCurrentTime())}),40))},Z.prototype.setSliderPosition=function(t){try{var e=this.controls.$slider.find(".ui-slider-handle"),o=Z.formatTimeForA11y(t,this.l10n);this.controls.$slider.slider("option","value",t),e.attr("aria-valuetext",o),e.attr("aria-valuenow",t.toString())}catch(t){return}},Z.prototype.updateInteractions=function(t){var e=this,o=Math.floor(10*t)/10;if(o!==e.lastTenth){void 0!==e.bookmarksMap&&void 0!==e.bookmarksMap[o]&&e.bookmarksMap[o].mouseover().mouseout();void 0!==e.endscreensMap&&void 0!==e.endscreensMap[o]&&e.currentState!==Z.SEEKING&&e.getAnsweredTotal()>0&&e.toggleEndscreen(!0)}e.lastTenth=o,e.toggleInteractions(t);var n=Math.floor(t);n!==e.lastSecond&&(e.currentState!==H5P.Video.PLAYING&&e.currentState!==H5P.Video.PAUSED||e.updateCurrentTime(n)),e.lastSecond=n},Z.prototype.updateCurrentTime=function(t){var e;if(null!==(e=this.controls)&&void 0!==e&&e.$currentTime){var o=this;t=Math.max(t,0);var n=Z.humanizeTime(t),i=Z.formatTimeForA11y(t,o.l10n);o.controls.$currentTime.html(n),o.controls.$currentTimeA11y.html("".concat(o.l10n.currentTime," ").concat(i)),o.controls.$currentTimeSimple.html(n),o.controls.$currentTimeA11ySimple.html("".concat(o.l10n.currentTime," ").concat(i))}},Z.prototype.getUsersScore=function(){for(var t=0,e=0;e0&&void 0!==arguments[0]?arguments[0]:".h5p-dialog-wrapper",e=this,o=e.$container.find(t);e.$tabbables=e.$container.find("a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]").filter((function(){var t=J(this),e=J.contains(o.get(0),t.get(0));if(t.data("tabindex"))return!0;if(!e){var n=t.attr("tabindex");return t.data("tabindex",n),t.attr("tabindex","-1"),!0}return!1}))},Z.prototype.restoreTabIndexes=function(t){var e=this;e.$tabbables&&(e.$tabbables.each((function(){var e=J(this),o=e.data("tabindex");if(t&&!J.contains(t.get(0),e.get(0)))return!0;e.hasClass("ui-slider-handle")?(e.attr("tabindex",0),e.removeData("tabindex")):void 0!==o?(e.attr("tabindex",o),e.removeData("tabindex")):e.removeAttr("tabindex")})),t||(e.$tabbables=void 0))},Z.prototype.toggleFocusTrap=function(){var t=this,e=this.getVisibleInteractions().filter((function(t){return t.getRequiresCompletion()&&!t.hasFullScore()}));e.length>0?this.$container.off("focusin").on("focusin",(function(o){return t.trapFocusInInteractions(e,J(o.target))})):this.$container.off("focusin","**")},Z.prototype.trapFocusInInteractions=function(t,e){var o=t.some((function(t){var o=t.getElement();return rt(o,e)})),n=!!this.$mask&&rt(this.$mask,e);if(!o&&!n){var i=t[0].getElement();i&&i.focus()}},Z.prototype.hideOverlayMask=function(){var t=this;return t.restoreTabIndexes(),t.dnb.dialog.closeOverlay(),t.$videoWrapper.removeClass("h5p-disable-opt-out"),t.toggleFocusTrap(),t.$container.find(".h5p-dialog-wrapper")},Z.prototype.showWarningMask=function(){var t=this,e="interactive-video-".concat(t.contentId,"-").concat(t.instanceIndex,"-completion-warning-text");return t.$mask||(t.$mask=J('
      \n
      \n
      ').concat(t.l10n.requiresCompletionWarning,'
      \n \n
      \n
      ")).click((function(){t.$mask.hide()})).appendTo(t.$container)),t.$mask.show(),t.$mask.find(".h5p-button-back").focus(),t.$mask},Z.prototype.setDisabled=function(t){return t.attr("aria-disabled","true").attr("tabindex","-1")},Z.prototype.isDisabled=function(t){return"true"===t.attr("aria-disabled")},Z.prototype.removeDisabled=function(t){return t.removeAttr("aria-disabled").attr("tabindex","0")},Z.prototype.hasUncompletedRequiredInteractions=function(t){return(void 0!==t?this.getVisibleInteractionsAt(t):this.getVisibleInteractions()).some((function(t){return t.getRequiresCompletion()&&!t.hasFullScore()}))},Z.prototype.getVisibleInteractions=function(){return this.interactions.filter((function(t){return t.isVisible()}))},Z.prototype.getVisibleInteractionsAt=function(t){return this.interactions.filter((function(e){return e.visibleAt(t)}))},Z.prototype.setUserSubmitted=function(t){"boolean"==typeof t&&(this.userSubmitted=t)},Z.prototype.showSolutions=function(){},Z.prototype.getAnswerGiven=function(){return!!this.userSubmitted&&this.interactions.some((function(t){var e,o;return null===(e=(o=t.getInstance()).getAnswerGiven)||void 0===e?void 0:e.call(o)}))},Z.prototype.getTitle=function(){return H5P.createTitle(this.contentData&&this.contentData.metadata&&this.contentData.metadata.title?this.contentData.metadata.title:"Interactive Video")},Z.prototype.findNextInteractionToShow=function(t,e){for(var o,n=0;nt||i.from==t&&(void 0===e||n>e))&&(void 0===o||i.from0&&void 0!==arguments[0]?arguments[0]:0,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.isSkippingProhibited(t)&&!e.force||(this.maxTimeReached=Math.max(this.maxTimeReached,t),this.nextInteractionToShow=this.nextInteractionToHide=void 0,this.video.seek(t))},Z.prototype.pause=function(){this.video&&this.video.pause&&this.video.pause()},Z.prototype.resetTask=function(){var t;this.interactionsProgress=[],this.dnb&&(this.dnb.calledFromResetTask=!0);for(var e=0;e0&&t.options.video.textTracks.videoTrack.forEach((function(t){t.track&&t.track.copyright&&e.addMedia(new H5P.MediaCopyright(t.track.copyright))})),e},Z.prototype.isSkippingProhibited=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return!this.editor&&("both"===this.preventSkippingMode||"none"!==this.preventSkippingMode&&this.maxTimeReached0?"".concat(o.hours," ").concat(e.hours,", "):"";return"".concat(n).concat(o.minutes," ").concat(e.minutes,", ").concat(o.seconds," ").concat(e.seconds)},Z.secondsToMinutesAndHours=function(t){var e=Math.floor(t/60);return{seconds:Math.floor(t%60),minutes:e%60,hours:Math.floor(e/60)}};var tt=function(t,e){e?t.setAttribute("tabindex","0"):t.removeAttribute("tabindex")},et=function(t,e){for(var o=0;o",{class:"h5p-video-info",text:t.l10n.howToCreateInteractions}).appendTo(t.$videoWrapper)),"VimeoPlayer"!==t.video.getHandlerName()||t.$videoPlaybutton||(t.$videoPlaybutton=J("
      ",{class:"h5p-play-button"}).appendTo(t.$videoWrapper)),t.$videoInfo.show(),t.$videoPlaybutton&&t.$videoPlaybutton.show()):(t.$videoWrapper.removeClass("heart-beat info-text"),t.$videoInfo&&t.$videoInfo.hide(),t.$videoPlaybutton&&t.$videoPlaybutton.hide(),t.editor.startGuidedTour()))};var it=function(t){var e=t.getVerifiedStatementValue(["object","definition"]);H5P.jQuery.extend(e,st())},st=function(){var t={interactionType:"compound",type:"http://adlnet.gov/expapi/activities/cmi.interaction",description:{"en-US":""}};return t},rt=function(t,e){return void 0!==t&&void 0!==e&&(t.is(e)||J.contains(t.get(0),e.get(0)))},at=function(t){return t.map((function(t){return t.getXAPIData()})).filter((function(t){return!!t}))};const ct=Z;H5P=H5P||{},H5P.InteractiveVideo=ct})();;