" ).appendTo( tooltip ),
id = tooltip.uniqueId().attr( "id" );
this._addClass( content, "ui-tooltip-content" );
this._addClass( tooltip, "ui-tooltip", "ui-widget ui-widget-content" );
tooltip.appendTo( this._appendTo( element ) );
return this.tooltips[ id ] = {
element: element,
tooltip: tooltip
};
},
_find: function( target ) {
var id = target.data( "ui-tooltip-id" );
return id ? this.tooltips[ id ] : null;
},
_removeTooltip: function( tooltip ) {
// Clear the interval for delayed tracking tooltips
clearInterval( this.delayedShow );
tooltip.remove();
delete this.tooltips[ tooltip.attr( "id" ) ];
},
_appendTo: function( target ) {
var element = target.closest( ".ui-front, dialog" );
if ( !element.length ) {
element = this.document[ 0 ].body;
}
return element;
},
_destroy: function() {
var that = this;
// Close open tooltips
$.each( this.tooltips, function( id, tooltipData ) {
// Delegate to close method to handle common cleanup
var event = $.Event( "blur" ),
element = tooltipData.element;
event.target = event.currentTarget = element[ 0 ];
that.close( event, true );
// Remove immediately; destroying an open tooltip doesn't use the
// hide animation
$( "#" + id ).remove();
// Restore the title
if ( element.data( "ui-tooltip-title" ) ) {
// If the title attribute has changed since open(), don't restore
if ( !element.attr( "title" ) ) {
element.attr( "title", element.data( "ui-tooltip-title" ) );
}
element.removeData( "ui-tooltip-title" );
}
} );
this.liveRegion.remove();
}
});
// DEPRECATED
// TODO: Switch return back to widget declaration at top of file when this is removed
if ( $.uiBackCompat !== false ) {
// Backcompat for tooltipClass option
$.widget( "ui.tooltip", $.ui.tooltip, {
options: {
tooltipClass: null
},
_tooltip: function() {
var tooltipData = this._superApply( arguments );
if ( this.options.tooltipClass ) {
tooltipData.tooltip.addClass( this.options.tooltipClass );
}
return tooltipData;
}
} );
}
var widgetsTooltip = $.ui.tooltip;
});
/*!
* jQuery UI Touch Punch 0.2.2
*
* Copyright 2011, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
(function ($) {
// Detect touch support
$.support.touch = ('ontouchend' in document || navigator.maxTouchPoints > 0);
// Ignore browsers without touch support
if (!$.support.touch) {
return;
}
var mouseProto = $.ui.mouse.prototype,
_mouseInit = mouseProto._mouseInit,
touchHandled;
/**
* Simulate a mouse event based on a corresponding touch event
* @param {Object} event A touch event
* @param {String} simulatedType The corresponding mouse event
*/
function simulateMouseEvent (event, simulatedType) {
// Ignore multi-touch events
if (event.originalEvent.touches.length > 1) {
return;
}
event.preventDefault();
var touch = event.originalEvent.changedTouches[0],
simulatedEvent = document.createEvent('MouseEvents');
// Initialize the simulated mouse event using the touch event's coordinates
simulatedEvent.initMouseEvent(
simulatedType, // type
true, // bubbles
true, // cancelable
window, // view
1, // detail
touch.screenX, // screenX
touch.screenY, // screenY
touch.clientX, // clientX
touch.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false, // metaKey
0, // button
null // relatedTarget
);
// Dispatch the simulated event to the target element
event.target.dispatchEvent(simulatedEvent);
}
/**
* Handle the jQuery UI widget's touchstart events
* @param {Object} event The widget element's touchstart event
*/
mouseProto._touchStart = function (event) {
var self = this;
// Ignore the event if another widget is already being handled
if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
return;
}
// Set the flag to prevent other widgets from inheriting the touch event
touchHandled = true;
// Track movement to determine if interaction was a click
self._touchMoved = false;
// Simulate the mouseover event
simulateMouseEvent(event, 'mouseover');
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
// Simulate the mousedown event
simulateMouseEvent(event, 'mousedown');
};
/**
* Handle the jQuery UI widget's touchmove events
* @param {Object} event The document's touchmove event
*/
mouseProto._touchMove = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Interaction was not a click
this._touchMoved = true;
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
};
/**
* Handle the jQuery UI widget's touchend events
* @param {Object} event The document's touchend event
*/
mouseProto._touchEnd = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Simulate the mouseup event
simulateMouseEvent(event, 'mouseup');
// Simulate the mouseout event
simulateMouseEvent(event, 'mouseout');
// If the touch interaction did not move, it should trigger a click
if (!this._touchMoved) {
// Simulate the click event
simulateMouseEvent(event, 'click');
}
// Unset the flag to allow other widgets to inherit the touch event
touchHandled = false;
};
/**
* A duck punch of the $.ui.mouse _mouseInit method to support touch events.
* This method extends the widget with bound touch event handlers that
* translate touch events to mouse events and pass them to the widget's
* original mouse event handling methods.
*/
mouseProto._mouseInit = function () {
var self = this;
// Delegate the touch handlers to the widget's element
self.element
.bind('touchstart', $.proxy(self, '_touchStart'))
.bind('touchmove', $.proxy(self, '_touchMove'))
.bind('touchend', $.proxy(self, '_touchEnd'));
// Call the original $.ui.mouse init method
_mouseInit.call(self);
};
})(jQuery);
(function (jQuery) {
// This is a hack to make ckeditor work inside modal dialogs. Since ckeditor dialogs are placed on body and not in the ui.dialog's DOM. See http://bugs.jqueryui.com/ticket/9087
jQuery.widget("ui.dialog", jQuery.ui.dialog, {
_allowInteraction: function (event) {
return true;
}
});
jQuery.ui.dialog.prototype._focusTabbable = function () {};
})(jQuery);
jQuery = oldJQuery;
;
/*
* flowplayer.js 3.2.12. The Flowplayer API
*
* Copyright 2009-2011 Flowplayer Oy
*
* This file is part of Flowplayer.
*
* Flowplayer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Flowplayer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Flowplayer. If not, see
.
*
* Date: ${date}
* Revision: ${revision}
*/
!function(){function h(p){console.log("$f.fireEvent",[].slice.call(p))}function l(r){if(!r||typeof r!="object"){return r}var p=new r.constructor();for(var q in r){if(r.hasOwnProperty(q)){p[q]=l(r[q])}}return p}function n(u,r){if(!u){return}var p,q=0,s=u.length;if(s===undefined){for(p in u){if(r.call(u[p],p,u[p])===false){break}}}else{for(var t=u[0];q
1){var u=arguments[1],r=(arguments.length==3)?arguments[2]:{};if(typeof u=="string"){u={src:u}}u=j({bgcolor:"#000000",version:[10,1],expressInstall:"http://releases.flowplayer.org/swf/expressinstall.swf",cachebusting:false},u);if(typeof p=="string"){if(p.indexOf(".")!=-1){var t=[];n(o(p),function(){t.push(new b(this,l(u),l(r)))});return new d(t)}else{var s=c(p);return new b(s!==null?s:l(p),l(u),l(r))}}else{if(p){return new b(p,l(u),l(r))}}}return null};j(window.$f,{fireEvent:function(){var q=[].slice.call(arguments);var r=$f(q[0]);return r?r._fireEvent(q.slice(1)):null},addPlugin:function(p,q){b.prototype[p]=q;return $f},each:n,extend:j});if(typeof jQuery=="function"){jQuery.fn.flowplayer=function(r,q){if(!arguments.length||typeof arguments[0]=="number"){var p=[];this.each(function(){var s=$f(this);if(s){p.push(s)}});return arguments.length?p[arguments[0]]:new d(p)}return this.each(function(){$f(this,l(r),q?l(q):{})})}}}();!function(){var h=document.all,j="http://get.adobe.com/flashplayer",c=typeof jQuery=="function",e=/(\d+)[^\d]+(\d+)[^\d]*(\d*)/,b={width:"100%",height:"100%",id:"_"+(""+Math.random()).slice(9),allowfullscreen:true,allowscriptaccess:"always",quality:"high",version:[3,0],onFail:null,expressInstall:null,w3c:false,cachebusting:false};if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){}})}function i(m,l){if(l){for(var f in l){if(l.hasOwnProperty(f)){m[f]=l[f]}}}return m}function a(f,n){var m=[];for(var l in f){if(f.hasOwnProperty(l)){m[l]=n(f[l])}}return m}window.flashembed=function(f,m,l){if(typeof f=="string"){f=document.getElementById(f.replace("#",""))}if(!f){return}if(typeof m=="string"){m={src:m}}return new d(f,i(i({},b),m),l)};var g=i(window.flashembed,{conf:b,getVersion:function(){var m,f;try{f=navigator.plugins["Shockwave Flash"].description.slice(16)}catch(o){try{m=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");f=m&&m.GetVariable("$version")}catch(n){try{m=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");f=m&&m.GetVariable("$version")}catch(l){}}}f=e.exec(f);return f?[1*f[1],1*f[(f[1]*1>9?2:3)]*1]:[0,0]},asString:function(l){if(l===null||l===undefined){return null}var f=typeof l;if(f=="object"&&l.push){f="array"}switch(f){case"string":l=l.replace(new RegExp('(["\\\\])',"g"),"\\$1");l=l.replace(/^\s?(\d+\.?\d*)%/,"$1pct");return'"'+l+'"';case"array":return"["+a(l,function(o){return g.asString(o)}).join(",")+"]";case"function":return'"function()"';case"object":var m=[];for(var n in l){if(l.hasOwnProperty(n)){m.push('"'+n+'":'+g.asString(l[n]))}}return"{"+m.join(",")+"}"}return String(l).replace(/\s/g," ").replace(/\'/g,'"')},getHTML:function(o,l){o=i({},o);var n='";if(o.w3c||h){n+=' '}o.width=o.height=o.id=o.w3c=o.src=null;o.onFail=o.version=o.expressInstall=null;for(var m in o){if(o[m]){n+=' '}}var p="";if(l){for(var f in l){if(l[f]){var q=l[f];p+=f+"="+(/function|object/.test(typeof q)?g.asString(q):q)+"&"}}p=p.slice(0,-1);n+=' "}n+=" ";return n},isSupported:function(f){return k[0]>f[0]||k[0]==f[0]&&k[1]>=f[1]}});var k=g.getVersion();function d(f,n,m){if(g.isSupported(n.version)){f.innerHTML=g.getHTML(n,m)}else{if(n.expressInstall&&g.isSupported([6,65])){f.innerHTML=g.getHTML(i(n,{src:n.expressInstall}),{MMredirectURL:encodeURIComponent(location.href),MMplayerType:"PlugIn",MMdoctitle:document.title})}else{if(!f.innerHTML.replace(/\s/g,"")){f.innerHTML="Flash version "+n.version+" or greater is required "+(k[0]>0?"Your version is "+k:"You have no flash plugin installed")+" "+(f.tagName=="A"?" Click here to download latest version
":"
Download latest version from here
");if(f.tagName=="A"||f.tagName=="DIV"){f.onclick=function(){location.href=j}}}if(n.onFail){var l=n.onFail.call(this);if(typeof l=="string"){f.innerHTML=l}}}}if(h){window[n.id]=document.getElementById(n.id)}i(this,{getRoot:function(){return f},getOptions:function(){return n},getConf:function(){return m},getApi:function(){return f.firstChild}})}if(c){jQuery.tools=jQuery.tools||{version:"3.2.12"};jQuery.tools.flashembed={conf:b};jQuery.fn.flashembed=function(l,f){return this.each(function(){$(this).data("flashembed",flashembed(this,l,f))})}}}();;
/** @namespace H5P */
H5P.VideoYouTube = (function ($) {
/**
* YouTube 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 YouTube(sources, options, l10n) {
var self = this;
var player;
var playbackRate = 1;
var id = 'h5p-youtube-' + numInstances;
numInstances++;
var $wrapper = $('
');
var $placeholder = $('
', {
id: id,
text: l10n.loading
}).appendTo($wrapper);
// Optional placeholder
// var $placeholder = $('
VIDEO ').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,
autoplay: options.autoplay ? 1 : 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: options.startAt,
playsinline: 1
},
events: {
onReady: function () {
self.trigger('ready');
self.trigger('loaded');
},
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) {
// 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);
}
}
});
};
/**
* 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
*/
self.pressToPlay = navigator.userAgent.match(/iPad/i) ? true : false;
/**
* 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 () {
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);
};
/**
* 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();
};
/**
* 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: '100%'
});
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;
var player;
var playbackRate = 1;
var id = 'h5p-panopto-' + numInstances;
numInstances++;
var $wrapper = $('
');
var $placeholder = $('
', {
id: id,
html: '
' + l10n.loading + '
'
}).appendTo($wrapper);
/**
* Use the Panopto API to create a new player
*
* @private
*/
var create = function () {
if (!$placeholder.is(':visible') || player !== undefined) {
return;
}
if (window.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: !!options.autoplay,
showbrand: false,
start: 0,
hideoverlay: !options.controls,
},
events: {
onIframeReady: function () {
$placeholder.children(0).text('');
player.loadVideo();
},
onReady: function () {
self.trigger('loaded');
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);
}
self.pause();
},
onStateChange: function (state) {
// TODO: Playback rate fix for IE11?
if (state > -1 && state < 4) {
self.trigger('stateChange', state);
}
},
onPlaybackRateChange: function () {
self.trigger('playbackRateChange', self.getPlaybackRate());
},
onError: function () {
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) {
return;
}
player.playVideo();
};
/**
* 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) {
return;
}
player.seekTo(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();
};
/**
* Check if video sound is turned on or off.
*
* @public
* @returns {Boolean}
*/
self.isMuted = function () {
if (!player || !player.isMuted) {
return;
}
return player.isMuted();
};
/**
* 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);
};
/**
* 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) {
console.log('Disable captions');
player.disableCaptions();
currentTrack = null;
}
else {
console.log('Set captions', track.value);
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;
}
// Use as much space as possible
$wrapper.css({
width: '100%',
height: '100%'
});
var width = $wrapper[0].clientWidth;
var height = options.fit ? $wrapper[0].clientHeight : (width * (9/16));
// Set size
$wrapper.css({
width: width + 'px',
height: height + 'px'
});
const $iframe = $placeholder.children('iframe');
if ($iframe.length) {
$iframe.attr('width', width);
$iframe.attr('height', height);
}
});
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\.[^\/]+)\/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/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 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);
video.autoplay = (options.autoplay ? true : 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 'stateChange':
if (lastState === arg) {
return; // Avoid firing event twice.
}
var validStartTime = options.startAt && options.startAt > 0;
if (arg === H5P.Video.PLAYING && validStartTime) {
video.currentTime = options.startAt;
delete options.startAt;
}
break;
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;
}
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();
}
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) {
if (lastState === undefined) {
// Make sure we always play before we seek to get an image.
// If not iOS devices will reset currentTime when pressing play.
video.play();
video.pause();
}
video.currentTime = time;
};
/**
* 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 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.VideoFlash = (function ($) {
/**
* Flash video player for H5P.
*
* @class
* @param {Array} sources Video files to use
* @param {Object} options Settings for the player
*/
function Flash(sources, options) {
var self = this;
// Player wrapper
var $wrapper = $('
', {
'class': 'h5p-video-flash',
css: {
width: '100%',
height: '100%'
}
});
/**
* 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;
// Sort sources into qualities
//var qualities = getQualities(sources);
var currentQuality;
// Create player options
var playerOptions = {
buffering: true,
clip: {
url: sources[0].path, // getPreferredQuality(),
autoPlay: options.autoplay,
autoBuffering: true,
scaling: 'fit',
onSeek: function () {
if (stateBeforeChangingQuality) {
// ????
}
},
onMetaData: function () {
setTimeout(function () {
if (stateBeforeChangingQuality !== undefined) {
fp.seek(currentTimeBeforeChangingQuality);
if (stateBeforeChangingQuality === H5P.Video.PLAYING) {
// Resume play
fp.play();
}
// Done changing quality
stateBeforeChangingQuality = undefined;
// Remove any errors
if ($error.is(':visible')) {
$error.remove();
}
}
else {
self.trigger('ready');
self.trigger('loaded');
}
}, 0); // Run on next tick
},
onBegin: function () {
self.trigger('stateChange', H5P.Video.PLAYING);
},
onResume: function () {
self.trigger('stateChange', H5P.Video.PLAYING);
},
onPause: function () {
self.trigger('stateChange', H5P.Video.PAUSED);
},
onFinish: function () {
self.trigger('stateChange', H5P.Video.ENDED);
},
onError: function (code, message) {
console.log('ERROR', code, message); // TODO
self.trigger('error', message);
}
},
plugins: {
controls: null
},
play: null, // Disable overlay controls
onPlaylistReplace: function () {
that.playlistReplaced();
}
};
if (options.controls) {
playerOptions.plugins.controls = {};
delete playerOptions.play;
}
var fp = flowplayer($wrapper[0], {
src: "http://releases.flowplayer.org/swf/flowplayer-3.2.16.swf",
wmode: "opaque"
}, playerOptions);
/**
* Appends the video player to the DOM.
*
* @public
* @param {jQuery} $container
*/
self.appendTo = function ($container) {
$wrapper.appendTo($container);
};
/**
* Get list of available qualities. Not available until after play.
*
* @public
* @returns {Array}
*/
self.getQualities = function () {
return;
};
/**
* 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;
}
// Keep track of current quality
currentQuality = quality;
self.trigger('qualityChange', currentQuality);
// Display throbber
self.trigger('stateChange', H5P.Video.BUFFERING);
// Change source
fp.setClip(qualities[quality].source.path);
fp.startBuffering();
};
/**
* Starts the video.
*
* @public
*/
self.play = function () {
if ($error.is(':visible')) {
return;
}
fp.play();
};
/**
* Pauses the video.
*
* @public
*/
self.pause = function () {
fp.pause();
};
/**
* Seek video to given time.
*
* @public
* @param {Number} time
*/
self.seek = function (time) {
fp.seek(time);
};
/**
* Get elapsed time since video beginning.
*
* @public
* @returns {Number}
*/
self.getCurrentTime = function () {
return fp.getTime();
};
/**
* Get total video duration time.
*
* @public
* @returns {Number}
*/
self.getDuration = function () {
return fp.getClip().metaData.duration;
};
/**
* Get percentage of video that is buffered.
*
* @public
* @returns {Number} Between 0 and 100
*/
self.getBuffered = function () {
return fp.getClip().buffer;
};
/**
* Turn off video sound.
*
* @public
*/
self.mute = function () {
fp.mute();
};
/**
* Turn on video sound.
*
* @public
*/
self.unMute = function () {
fp.unmute();
};
/**
* Check if video sound is turned on or off.
*
* @public
* @returns {Boolean}
*/
self.isMuted = function () {
return fp.muted;
};
/**
* Returns the video sound level.
*
* @public
* @returns {Number} Between 0 and 100.
*/
self.getVolume = function () {
return fp.volumeLevel * 100;
};
/**
* Set video sound level.
*
* @public
* @param {Number} volume Between 0 and 100.
*/
self.setVolume = function (level) {
fp.volume(level / 100);
};
// Handle resize events
self.on('resize', function () {
var $object = H5P.jQuery(fp.getParent()).children('object');
var clip = fp.getClip();
if (clip !== undefined) {
$object.css('height', $object.width() * (clip.metaData.height / clip.metaData.width));
}
});
}
/**
* Check to see if we can play any of the given sources.
*
* @public
* @static
* @param {Array} sources
* @returns {Boolean}
*/
Flash.canPlay = function (sources) {
// Cycle through sources
for (var i = 0; i < sources.length; i++) {
if (sources[i].mime === 'video/mp4' || /\.mp4$/.test(sources[i].mime)) {
return true; // We only play mp4
}
}
};
return Flash;
})(H5P.jQuery);
// Register video handler
H5P.videoHandlers = H5P.videoHandlers || [];
H5P.videoHandlers.push(H5P.VideoFlash);
;
/** @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
*/
function Video(parameters, id) {
var self = this;
self.contentId = id;
// 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.',
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, parameters.visuals, {
disableFullscreen: false
});
/** @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);
}
});
/**
* 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').html('');
if (self.appendTo !== undefined) {
self.appendTo($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;
};
// Resize the video when we know its aspect ratio
self.on('loaded', function () {
self.trigger('resize');
});
// 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,
startAt: parameters.startAt || 0,
tracks: tracks,
disableRemotePlayback: parameters.visuals.disableRemotePlayback === true,
disableFullscreen: parameters.visuals.disableFullscreen === true
}
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;
};
/** @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 || []);
;
var H5P = H5P || {};
/**
* A class that easily helps your create awesome drag and drop.
*
* @param {H5P.DragNBar} DnB
* @param {jQuery} $container
* @returns {undefined}
*/
H5P.DragNDrop = function (dnb, $container) {
H5P.EventDispatcher.call(this);
this.dnb = dnb;
this.$container = $container;
this.scrollLeft = 0;
this.scrollTop = 0;
};
// Inherit support for events
H5P.DragNDrop.prototype = Object.create(H5P.EventDispatcher.prototype);
H5P.DragNDrop.prototype.constructor = H5P.DragNDrop;
/**
* Set the current element
*
* @method setElement
* @param {j@uery} $element
*/
H5P.DragNDrop.prototype.setElement = function ($element) {
this.$element = $element;
};
/**
* Start tracking the mouse.
*
* @param {jQuery} $element
* @param {Number} x Start X coordinate
* @param {Number} y Start Y coordinate
* @returns {undefined}
*/
H5P.DragNDrop.prototype.press = function ($element, x, y) {
var that = this;
var eventData = {
instance: this
};
H5P.$window
.mousemove(eventData, H5P.DragNDrop.moveHandler)
.bind('mouseup', eventData, H5P.DragNDrop.release);
H5P.$body
// With user-select: none uncommented, after moving a drag and drop element, if I hover over something that changes transparancy on hover IE10 on WIN7 crashes
// TODO: Add user-select and -ms-user-select later if IE10 stops bugging
.css({'-moz-user-select': 'none', '-webkit-user-select': 'none'/*, 'user-select': 'none', '-ms-user-select': 'none'*/})
.attr('unselectable', 'on')[0]
.onselectstart = H5P.$body[0].ondragstart = function () {
return false;
};
that.containerOffset = $element.offsetParent().offset();
this.$element = $element;
this.moving = false;
this.startX = x;
this.startY = y;
this.marginX = parseInt($element.css('marginLeft')) + parseInt($element.css('marginRight'));
this.marginY = parseInt($element.css('marginTop')) + parseInt($element.css('marginBottom'));
var offset = $element.offset();
this.adjust = {
x: x - offset.left + this.marginX,
y: y - offset.top - this.marginY
};
if (that.dnb && that.dnb.newElement) {
this.move(x, y);
}
};
/**
* Handles mouse move events.
*
* @param {Event} event
*/
H5P.DragNDrop.moveHandler = function (event) {
event.stopPropagation();
event.data.instance.move(event.pageX, event.pageY);
};
/**
* Handles mouse movements.
*
* @param {number} x
* @param {number} y
*/
H5P.DragNDrop.prototype.move = function (x, y) {
var that = this;
if (!that.moving) {
if (that.startMovingCallback !== undefined && !that.startMovingCallback(x, y)) {
return;
}
// Start moving
that.moving = true;
that.$element.addClass('h5p-moving');
}
x -= that.adjust.x;
y -= that.adjust.y;
var posX = x - that.containerOffset.left + that.scrollLeft;
var posY = y - that.containerOffset.top + that.scrollTop;
if (that.snap !== undefined) {
posX = Math.round(posX / that.snap) * that.snap;
posY = Math.round(posY / that.snap) * that.snap;
}
// Do not move outside of minimum values.
if (that.min !== undefined) {
if (posX < that.min.x) {
posX = that.min.x;
x = that.min.x + that.containerOffset.left - that.scrollLeft;
}
if (posY < that.min.y) {
posY = that.min.y;
y = that.min.y + that.containerOffset.top - that.scrollTop;
}
}
if (that.dnb && that.dnb.newElement && posY >= 0) {
that.min.y = 0;
}
// Do not move outside of maximum values.
if (that.max !== undefined) {
if (posX > that.max.x) {
posX = that.max.x;
x = that.max.x + that.containerOffset.left - that.scrollLeft;
}
if (posY > that.max.y) {
posY = that.max.y;
y = that.max.y + that.containerOffset.top - that.scrollTop;
}
}
// Show transform panel if element has moved
var startX = that.startX - that.adjust.x - that.containerOffset.left + that.scrollLeft;
var startY = that.startY - that.adjust.y - that.containerOffset.top + that.scrollTop;
if (!that.snap && (posX !== startX || posY !== startY)) {
that.trigger('showTransformPanel');
}
else if (that.snap) {
var xChanged = (Math.round(posX / that.snap) * that.snap) !==
(Math.round(startX / that.snap) * that.snap);
var yChanged = (Math.round(posY / that.snap) * that.snap) !==
(Math.round(startY / that.snap) * that.snap);
if (xChanged || yChanged) {
that.trigger('showTransformPanel');
}
}
that.$element.css({left: posX, top: posY});
if (that.dnb) {
that.dnb.updateCoordinates();
}
if (that.moveCallback !== undefined) {
that.moveCallback(x, y, that.$element);
}
};
/**
* Stop tracking the mouse.
*
* @param {Object} event
* @returns {undefined}
*/
H5P.DragNDrop.release = function (event) {
var that = event.data.instance;
H5P.$window
.unbind('mousemove', H5P.DragNDrop.moveHandler)
.unbind('mouseup', H5P.DragNDrop.release);
H5P.$body
.css({'-moz-user-select': '', '-webkit-user-select': ''/*, 'user-select': '', '-ms-user-select': ''*/})
.removeAttr('unselectable')[0]
.onselectstart = H5P.$body[0].ondragstart = null;
if (that.releaseCallback !== undefined) {
that.releaseCallback();
}
if (that.moving) {
that.$element.removeClass('h5p-moving');
if (that.stopMovingCallback !== undefined) {
that.stopMovingCallback(event);
}
}
// trigger to hide the transform panel unless it was activated
// through the context menu
that.trigger('hideTransformPanel');
};
;
/*global H5P*/
H5P.DragNResize = (function ($, EventDispatcher) {
/**
* Constructor!
*
* @class H5P.DragNResize
* @param {H5P.jQuery} $container
*/
function C($container) {
var self = this;
this.$container = $container;
self.disabledModifiers = false;
this.minSize = H5P.DragNResize.MIN_SIZE;
EventDispatcher.call(this);
// Override settings for snapping to grid, and locking aspect ratio.
H5P.$body.keydown(function (event) {
if (self.disabledModifiers) {
return;
}
if (event.keyCode === 17) {
// Ctrl
self.revertSnap = true;
}
else if (event.keyCode === 16) {
// Shift
self.revertLock = true;
}
}).keyup(function (event) {
if (self.disabledModifiers) {
return;
}
if (event.keyCode === 17) {
// Ctrl
self.revertSnap = false;
}
else if (event.keyCode === 16) {
// Shift
self.revertLock = false;
}
});
}
// Inheritance
C.prototype = Object.create(EventDispatcher.prototype);
C.prototype.constructor = C;
/**
* Gives the given element a resize handle.
*
* @param {H5P.jQuery} $element
* @param {Object} [options]
* @param {boolean} [options.lock]
* @param {boolean} [options.cornerLock]
* @param {string} [options.directionLock]
* @param {number} [options.minSize]
*/
C.prototype.add = function ($element, options) {
var that = this;
// Array with position of handles
var cornerPositions = ['nw', 'ne', 'sw', 'se'];
var horizontalEdgePositions = ['w', 'e'];
var verticalEdgePositions = ['n', 's'];
var addResizeHandle = function (position, corner) {
$('
', {
'class': 'h5p-dragnresize-handle ' + position
}).mousedown(function (event) {
that.lock = (options && (options.lock || corner && options.cornerLock));
if (options.cornerLock) {
that.isImage = true;
}
that.$element = $element;
that.press(event.clientX, event.clientY, position, options.minSize);
}).data('position', position)
.appendTo($element);
};
if (!options.directionLock) {
cornerPositions.forEach(function (pos) {
addResizeHandle(pos, true);
});
}
// Add edge handles
if (!options || !options.lock) {
if (options.directionLock != "vertical") {
horizontalEdgePositions.forEach(function (pos) {
addResizeHandle(pos);
});
}
if (options.directionLock != "horizontal") {
verticalEdgePositions.forEach(function (pos) {
addResizeHandle(pos);
});
}
}
if (options.minSize) {
this.minSize = options.minSize;
}
else {
this.minSize = H5P.DragNResize.MIN_SIZE;
}
};
/**
* Get paddings for the element
*/
C.prototype.getElementPaddings = function () {
return {
horizontal: Number(this.$element.css('padding-left').replace("px", "")) + Number(this.$element.css('padding-right').replace("px", "")),
vertical: Number(this.$element.css('padding-top').replace("px", "")) + Number(this.$element.css('padding-bottom').replace("px", ""))
};
};
/**
* Get borders for the element
* @returns {{horizontal: number, vertical: number}}
*/
C.prototype.getElementBorders = function () {
return {
horizontal: Number(this.$element.css('border-left-width').replace('px', '')) + Number(this.$element.css('border-right-width').replace('px', '')),
vertical: Number(this.$element.css('border-top-width').replace('px', '')) + Number(this.$element.css('border-bottom-width').replace('px', ''))
};
};
C.prototype.setContainerEm = function (containerEm) {
this.containerEm = containerEm;
};
/**
* Start resizing
*
* @param {number} x
* @param {number} y
* @param {String} [direction] Direction of resize
* @param {number} minSize
*/
C.prototype.press = function (x, y, direction, minSize) {
this.active = true;
var eventData = {
instance: this,
direction: direction
};
this.minSize = (minSize ? minSize : H5P.DragNResize.MIN_SIZE);
H5P.$window
.bind('mouseup', eventData, C.release)
.mousemove(eventData, C.move);
H5P.$body
.css({
'-moz-user-select': 'none',
'-webkit-user-select': 'none',
'user-select': 'none',
'-ms-user-select': 'none'
})
.attr('unselectable', 'on')[0]
.onselectstart = H5P.$body[0].ondragstart = function () {
return false;
};
this.startX = x;
this.startY = y;
this.padding = this.getElementPaddings();
this.borders = this.getElementBorders();
this.startWidth = this.$element.outerWidth();
this.startHeight = this.$element.outerHeight();
this.ratio = (this.startWidth / this.startHeight);
var position = this.$element.position();
this.left = position.left;
this.top = position.top;
this.containerWidth = this.$container.width();
this.containerHeight = this.$container.height();
// Set default values
this.newLeft = this.left;
this.newTop = this.top;
this.newWidth = this.startWidth;
this.newHeight = this.startHeight;
this.trigger('startResizing', eventData);
// Show transform panel
this.trigger('showTransformPanel');
};
/**
* Resize events
*
* @param {Event} event
*/
C.move = function (event) {
var direction = (event.data.direction ? event.data.direction : 'se');
var that = event.data.instance;
var moveW = (direction === 'nw' || direction === 'sw' || direction === 'w');
var moveN = (direction === 'nw' || direction === 'ne' || direction === 'n');
var moveDiagonally = (direction === 'nw' || direction === 'ne' || direction === 'sw' || direction === 'se');
var movesHorizontal = (direction === 'w' || direction === 'e');
var movesVertical = (direction === 'n' || direction === 's');
var deltaX = that.startX - event.clientX;
var deltaY = that.startY - event.clientY;
that.minLeft = that.left + that.startWidth - that.minSize;
that.minTop = that.top + that.startHeight - that.minSize;
// Moving west
if (moveW) {
that.newLeft = that.left - deltaX;
that.newWidth = that.startWidth + deltaX;
// Check edge cases
if (that.newLeft < 0) {
that.newLeft = 0;
that.newWidth = that.left + that.startWidth;
}
else if (that.newLeft > that.minLeft) {
that.newLeft = that.minLeft;
that.newWidth = that.left - that.minLeft + that.startWidth;
}
// Snap west side
if (that.snap && !that.revertSnap) {
that.newLeft = Math.round(that.newLeft / that.snap) * that.snap;
// Make sure element does not snap east
if (that.newLeft > that.minLeft) {
that.newLeft = Math.floor(that.minLeft / that.snap) * that.snap;
}
that.newWidth = (that.left - that.newLeft) + that.startWidth;
}
}
else if (!movesVertical) {
that.newWidth = that.startWidth - deltaX;
// Snap width
if (that.snap && !that.revertSnap) {
that.newWidth = Math.round(that.newWidth / that.snap) * that.snap;
}
if (that.left + that.newWidth > that.containerWidth) {
that.newWidth = that.containerWidth - that.left;
}
}
// Moving north
if (moveN) {
that.newTop = that.top - deltaY;
that.newHeight = that.startHeight + deltaY;
// Check edge cases
if (that.newTop < 0) {
that.newTop = 0;
that.newHeight = that.top + that.startHeight;
}
else if (that.newTop > that.minTop) {
that.newTop = that.minTop;
that.newHeight = that.top - that.minTop + that.startHeight;
}
// Snap north
if (that.snap && !that.revertSnap) {
that.newTop = Math.round(that.newTop / that.snap) * that.snap;
// Make sure element does not snap south
if (that.newTop > that.minTop) {
that.newTop = Math.floor(that.minTop / that.snap) * that.snap;
}
that.newHeight = (that.top - that.newTop) + that.startHeight;
}
}
else if (!movesHorizontal) {
that.newHeight = that.startHeight - deltaY;
// Snap height
if (that.snap && !that.revertSnap) {
that.newHeight = Math.round(that.newHeight / that.snap) * that.snap;
}
if (that.top + that.newHeight > that.containerHeight) {
that.newHeight = that.containerHeight - that.top;
}
}
// Set min size
if (that.newWidth <= that.minSize) {
that.newWidth = that.minSize;
}
if (that.newHeight <= that.minSize) {
that.newHeight = that.minSize;
}
// Apply ratio lock for elements except images, they have a their own specific for corner cases
var lock = (that.revertLock ? !that.lock : that.lock);
if (lock && (moveDiagonally || !that.isImage)) {
that.lockDimensions(moveW, moveN, movesVertical, movesHorizontal);
}
// Reduce size by padding and borders
that.finalWidth = that.newWidth;
that.finalHeight = that.newHeight;
if (that.$element.css('boxSizing') !== 'border-box') {
that.finalWidth -= (that.padding.horizontal + that.borders.horizontal);
that.finalHeight -= (that.padding.vertical + that.borders.vertical);
}
that.$element.css({
width: (that.finalWidth / that.containerEm) + 'em',
height: (that.finalHeight / that.containerEm) + 'em',
left: ((that.newLeft / that.containerWidth) * 100) + '%',
top: ((that.newTop / that.containerHeight) * 100) + '%'
});
that.trigger('moveResizing');
};
/**
* Changes element values depending on moving direction of the element
* @param isMovingWest
* @param isMovingNorth
* @param movesVertical
* @param movesHorizontal
*/
C.prototype.lockDimensions = function (isMovingWest, isMovingNorth, movesVertical, movesHorizontal) {
var self = this;
// Cap movement at top
var lockTop = function (isMovingNorth) {
if (!isMovingNorth) {
return;
}
self.newTop = self.top - (self.newHeight - self.startHeight);
// Edge case
if (self.newTop <= 0) {
self.newTop = 0;
}
};
// Expand to longest edge
if (movesVertical) {
this.newWidth = this.newHeight * this.ratio;
// Make sure locked ratio does not cause size to go below min size
if (this.newWidth < this.minSize) {
this.newWidth = this.minSize;
this.newHeight = this.minSize / this.ratio;
}
}
else if (movesHorizontal) {
this.newHeight = this.newWidth / this.ratio;
// Make sure locked ratio does not cause size to go below min size
if (this.newHeight < this.minSize) {
this.newHeight = this.minSize;
this.newWidth = this.minSize * this.ratio;
}
}
else if (this.newWidth / this.startWidth > this.newHeight / this.startHeight) {
// Expand to width
this.newHeight = this.newWidth / this.ratio;
}
else {
// Expand to height
this.newWidth = this.newHeight * this.ratio;
}
// Change top to match new height
if (isMovingNorth) {
lockTop(isMovingNorth);
if (self.newTop <= 0) {
self.newHeight = self.top + self.startHeight;
self.newWidth = self.newHeight * self.ratio;
}
}
else {
// Too high
if (this.top + this.newHeight > this.containerHeight) {
this.newHeight = this.containerHeight - this.top;
this.newWidth = this.newHeight * this.ratio;
}
}
// Change left to match new width
if (isMovingWest) {
this.newLeft = this.left - (this.newWidth - this.startWidth);
// Edge case
if (this.newLeft <= 0) {
this.newLeft = 0;
this.newWidth = this.left + this.startWidth;
this.newHeight = this.newWidth / this.ratio;
}
}
else {
// Too wide
if (this.left + this.newWidth > this.containerWidth) {
this.newWidth = this.containerWidth - this.left;
this.newHeight = this.newWidth / this.ratio;
}
}
// Need to re-lock top in case height changed
lockTop(isMovingNorth);
};
/**
* Stop resizing
*
* @param {Event} event
*/
C.release = function (event) {
var that = event.data.instance;
that.active = false;
H5P.$window
.unbind('mouseup', C.release)
.unbind('mousemove', C.move);
H5P.$body
.css({
'-moz-user-select': '',
'-webkit-user-select': '',
'user-select': '',
'-ms-user-select': ''
})
.removeAttr('unselectable')[0]
.onselectstart = H5P.$body[0].ondragstart = null;
if (that.newWidth !== that.startWidth ||
that.newHeight !== that.startHeight) {
// Stopped resizing send width and height in Ems
that.trigger('stoppedResizing', {
left: that.newLeft,
top: that.newTop,
width: that.finalWidth / that.containerEm,
height: that.finalHeight / that.containerEm
});
}
// Refocus element after resizing it. Apply timeout since focus is lost at the end of mouse event.
setTimeout(function () {
that.$element.focus();
}, 0);
// trigger to hide the transform panel unless it was activated
// through the context menu
that.trigger('hideTransformPanel');
};
/**
* Toggle modifiers when we are not interacting with drag objects.
* @param {boolean} [enable]
*/
C.prototype.toggleModifiers = function (enable) {
this.disabledModifiers = enable === undefined ? !this.disabledModifiers : !enable;
};
C.MIN_SIZE = 24;
return C;
})(H5P.jQuery, H5P.EventDispatcher);
;
H5P.DragNBar = (function (EventDispatcher) {
var nextInstanceIndex = 0;
/**
* Constructor. Initializes the drag and drop menu bar.
*
* @class
* @param {Array} buttons
* @param {H5P.jQuery} $container
* @param {H5P.jQuery} $dialogContainer
* @param {object} [options] Collection of options
* @param {boolean} [options.disableEditor=false] Determines if DragNBar should be displayed in view or editor mode
* @param {boolean} [options.enableCopyPaste=true] Determines if copy & paste is supported
* @param {H5P.jQuery} [options.$blurHandlers] When clicking these element(s) dnb focus will be lost
* @param {object} [options.libraries] Libraries to check against for paste notification
*/
function DragNBar(buttons, $container, $dialogContainer, options) {
var self = this;
EventDispatcher.call(this);
this.overflowThreshold = 13; // How many buttons to display before we add the more button.
this.buttons = buttons;
this.$container = $container;
this.$dialogContainer = $dialogContainer;
this.dnd = new H5P.DragNDrop(this, $container);
this.dnd.snap = 10;
this.newElement = false;
this.enabled = true;
var defaultOptions = {
disableEditor: false,
enableCopyPaste: true
};
options = H5P.jQuery.extend(defaultOptions, options);
this.enableCopyPaste = options.enableCopyPaste;
this.isEditor = !options.disableEditor;
this.$blurHandlers = options.$blurHandlers ? options.$blurHandlers : undefined;
this.libraries = options.libraries;
this.instanceIndex = nextInstanceIndex++;
/**
* Keeps track of created DragNBar elements
* @type {Array}
*/
this.elements = [];
// Create a popup dialog
this.dialog = new H5P.DragNBarDialog($dialogContainer, $container);
// Fix for forcing redraw on $container, to avoid "artifcats" on safari
this.$container.addClass('hardware-accelerated');
if (this.isEditor) {
this.transformButtonActive = false;
this.initEditor();
this.initClickListeners();
H5P.$window.resize(function () {
self.resize();
});
}
/**
* Add button group.
*
* @private
* @param {object[]} Buttons.
* @param {H5P.jQuery} $button Button to add button group to.
* @param {object} [options] Options.
* @param {string} [options.title] Title for the group.
*/
this.addButtonGroup = function (buttons, $button, options) {
const $buttonGroup = H5P.jQuery('
');
// Add optional title to the group
if (options && options.title && options.title !=='') {
H5P.jQuery('
' + options.title + '
')
.appendTo($buttonGroup);
}
// Container for buttons
const $buttonGroupButtons = H5P.jQuery('
')
.appendTo($buttonGroup);
// Add buttons
buttons.forEach(function (button) {
self.addButton(button, $buttonGroupButtons);
});
$buttonGroup.insertAfter($button.parent());
return $buttonGroup;
};
}
// Inherit support for events
DragNBar.prototype = Object.create(EventDispatcher.prototype);
DragNBar.prototype.constructor = DragNBar;
return DragNBar;
})(H5P.EventDispatcher);
/**
* Initializes editor functionality of DragNBar
*/
H5P.DragNBar.prototype.initEditor = function () {
var that = this;
this.dnr = new H5P.DragNResize(this.$container);
this.dnr.snap = 10;
// Update coordinates when element is resized
this.dnr.on('moveResizing', function () {
var offset = that.$element.offset();
var position = that.$element.position();
that.updateCoordinates(offset.left, offset.top, position.left, position.top);
});
// Set pressed to not lose focus at the end of resize
this.dnr.on('stoppedResizing', function () {
that.pressed = true;
// Delete pressed after dnbelement has been refocused so it will lose focus on single click.
setTimeout(function () {
delete that.pressed;
}, 10);
});
/**
* Show transform panel listeners
*/
this.dnr.on('showTransformPanel', function () {
TransformPanel(true);
});
this.dnd.on('showTransformPanel', function () {
TransformPanel(true);
});
/**
* Hide transform panel listeners
*/
this.dnr.on('hideTransformPanel', function () {
if (!that.transformButtonActive) {
TransformPanel(false);
}
});
this.dnd.on('hideTransformPanel', function () {
if (!that.transformButtonActive) {
TransformPanel(false);
}
});
/**
* Trigger a context menu transform to either show or hide
* the transform panel.
*
* @param {boolean} show
*/
function TransformPanel(show) {
if (that.focusedElement) {
that.focusedElement.contextMenu.trigger('contextMenuTransform', {showTransformPanel: show});
}
}
this.dnd.startMovingCallback = function () {
that.dnd.min = {x: 0, y: 0};
that.dnd.max = {
x: that.$container.width() - that.$element.outerWidth(),
y: that.$container.height() - that.$element.outerHeight()
};
if (that.newElement) {
that.dnd.adjust.x = 10;
that.dnd.adjust.y = 10;
that.dnd.min.y -= that.$list.height();
}
return true;
};
this.dnd.stopMovingCallback = function () {
var pos = {};
if (that.newElement) {
that.$container.css('overflow', '');
if (Math.round(parseFloat(that.$element.css('top'))) < 0) {
// Try to center element, but avoid overlapping
pos.x = (that.dnd.max.x / 2);
pos.y = (that.dnd.max.y / 2);
that.avoidOverlapping(pos, that.$element);
}
}
if (pos.x === undefined || pos.y === undefined ) {
pos.x = Math.round(parseFloat(that.$element.css('left')));
pos.y = Math.round(parseFloat(that.$element.css('top')));
}
that.stopMoving(pos.x, pos.y);
that.newElement = false;
delete that.dnd.min;
delete that.dnd.max;
};
};
/**
* Tries to position the given element close to the requested coordinates.
* Element can be skipped to check if spot is available.
*
* @param {object} pos
* @param {number} pos.x
* @param {number} pos.y
* @param {(H5P.jQuery|Object)} element object with width&height if ran before insertion.
*/
H5P.DragNBar.prototype.avoidOverlapping = function (pos, $element) {
// Determine size of element
var size = $element;
if (size instanceof H5P.jQuery) {
size = window.getComputedStyle(size[0]);
size = {
width: parseFloat(size.width),
height: parseFloat(size.height)
};
}
else {
$element = undefined;
}
// Determine how much they can be manuvered
var containerStyle = window.getComputedStyle(this.$container[0]);
var manX = parseFloat(containerStyle.width) - size.width;
var manY = parseFloat(containerStyle.height) - size.height;
var limit = 16;
var attempts = 0;
while (attempts < limit && this.elementOverlaps(pos.x, pos.y, $element)) {
// Try to place randomly inside container
if (manX > 0) {
pos.x = Math.floor(Math.random() * manX);
}
if (manY > 0) {
pos.y = Math.floor(Math.random() * manY);
}
attempts++;
}
};
/**
* Determine if moving the given element to its new position will cause it to
* cover another element. This can make new or pasted elements difficult to see.
* Element can be skipped to check if spot is available.
*
* @param {number} x
* @param {number} y
* @param {H5P.jQuery} [$element]
* @returns {boolean}
*/
H5P.DragNBar.prototype.elementOverlaps = function (x, y, $element) {
var self = this;
// Use snap grid
x = Math.round(x / 10);
y = Math.round(y / 10);
for (var i = 0; i < self.elements.length; i++) {
var element = self.elements[i];
if ($element !== undefined && element.$element === $element) {
continue;
}
if (x === Math.round(parseFloat(element.$element.css('left')) / 10) &&
y === Math.round(parseFloat(element.$element.css('top')) / 10)) {
return true; // Stop loop
}
}
return false;
};
// Key coordinates
var SHIFT = 16;
var CTRL = 17;
var DELETE = 46;
var BACKSPACE = 8;
var C = 67;
var V = 86;
var LEFT = 37;
var UP = 38;
var RIGHT = 39;
var DOWN = 40;
// Keep track of key state
var ctrlDown = false;
// How many pixels to move
var snapAmount = 1;
/**
* Handle keydown events for the entire frame
*/
H5P.DragNBar.keydownHandler = function (event) {
var self = event.data.instance;
var activeElement = document.activeElement;
// Don't care about keys if parent editor is not in focus
// This means all editors using drag-n-bar need to set a tabindex
// (it's not done inside this library)
if (self.$dialogContainer.find(activeElement).length === 0 && self.$dialogContainer.get(0) !== activeElement) {
return;
}
if (event.which === CTRL) {
ctrlDown = true;
if (self.dnd.snap !== undefined) {
// Disable snapping
delete self.dnd.snap;
}
}
if (event.which === SHIFT) {
snapAmount = self.dnd.snap;
}
if (event.which === LEFT && self.focusedElement) {
if (activeElement.contentEditable === 'true' || activeElement.value !== undefined) {
return;
}
event.preventDefault();
self.moveWithKeys(-snapAmount, 0);
}
else if (event.which === UP && self.focusedElement) {
if (activeElement.contentEditable === 'true' || activeElement.value !== undefined) {
return;
}
event.preventDefault();
self.moveWithKeys(0, -snapAmount);
}
else if (event.which === RIGHT && self.focusedElement) {
if (activeElement.contentEditable === 'true' || activeElement.value !== undefined) {
return;
}
event.preventDefault();
self.moveWithKeys(snapAmount, 0);
}
else if (event.which === DOWN && self.focusedElement) {
if (activeElement.contentEditable === 'true' || activeElement.value !== undefined) {
return;
}
event.preventDefault();
self.moveWithKeys(0, snapAmount);
}
else if (event.which === C && ctrlDown && self.focusedElement && self.$container.is(':visible')) {
self.copyHandler(event);
}
else if (event.which === V && ctrlDown && window.localStorage && self.$container.is(':visible')) {
self.pasteHandler(event);
}
else if ((event.which === DELETE || event.which === BACKSPACE) && self.focusedElement && self.$container.is(':visible') && activeElement.tagName.toLowerCase() !== 'input') {
if (self.pressed === undefined) {
self.focusedElement.contextMenu.trigger('contextMenuRemove');
event.preventDefault(); // Prevent browser navigating back
}
}
};
/**
* Copy object.
* @param {Event} event - Event to check for copyable content.
*/
H5P.DragNBar.prototype.copyHandler = function (event) {
if (!this.enableCopyPaste) {
return;
}
var self = event === undefined ? this : event.data.instance;
// Copy element params to clipboard
var elementSize = window.getComputedStyle(self.focusedElement.$element[0]);
var width = parseFloat(elementSize.width);
var height = parseFloat(elementSize.height) / width;
width = width / (parseFloat(window.getComputedStyle(self.$container[0]).width) / 100);
height *= width;
self.focusedElement.toClipboard(width, height);
H5P.externalDispatcher.trigger('datainclipboard', {reset: false});
};
/**
* Paste object.
* @param {Event} event - Event to check for pastable content.
*/
H5P.DragNBar.prototype.pasteHandler = function (event) {
var self = event === undefined ? this : event.data.instance;
var activeElement = document.activeElement;
// Don't paste if parent editor is not in focus
if (!this.enableCopyPaste || self.preventPaste || self.dialog.isOpen() ||
activeElement.contentEditable === 'true' || activeElement.value !== undefined) {
return;
}
if (self.$pasteButton.hasClass('disabled')) {
// Inform user why pasting is not possible
const pasteCheck = H5PEditor.canPastePlus(H5P.getClipboard(), this.libraries);
if (pasteCheck.canPaste !== true) {
if (pasteCheck.reason === 'pasteTooOld' || pasteCheck.reason === 'pasteTooNew') {
self.confirmPasteError(pasteCheck.description, 0, function () {});
}
else {
H5PEditor.attachToastTo(
self.$pasteButton.get(0),
pasteCheck.description,
{position: {horizontal: 'center', vertical: 'above', noOverflowX: true}}
);
}
return;
}
}
var clipboardData = localStorage.getItem('h5pClipboard');
if (clipboardData) {
// Parse
try {
clipboardData = JSON.parse(clipboardData);
}
catch (err) {
console.error('Unable to parse JSON from clipboard.', err);
return;
}
// Update file URLs
H5P.DragNBar.updateFileUrls(clipboardData.specific, function (path) {
var isTmpFile = (path.substr(-4,4) === '#tmp');
if (!isTmpFile && clipboardData.contentId) {
// Comes from existing content
let prefix;
if (H5PEditor.contentId) {
// .. to existing content
prefix = '../' + clipboardData.contentId + '/';
}
else {
// .. to new content
prefix = (H5PEditor.contentRelUrl ? H5PEditor.contentRelUrl : '../content/') + clipboardData.contentId + '/';
}
return path.substr(0, prefix.length) === prefix ? path : prefix + path;
}
return path; // Will automatically be looked for in tmp folder
});
if (clipboardData.generic) {
// Use reference instead of key
clipboardData.generic = clipboardData.specific[clipboardData.generic];
// Avoid multiple content with same ID
delete clipboardData.generic.subContentId;
}
self.trigger('paste', clipboardData);
}
};
/**
* Set state of paste button.
* @param {boolean} canPaste - If true, button will be enabled
*/
H5P.DragNBar.prototype.setCanPaste = function (canPaste) {
canPaste = canPaste || false;
if (this.$pasteButton) {
this.$pasteButton.toggleClass('disabled', !canPaste);
}
};
/**
* Confirm replace if there is content selected
*
* @param {number} top Offset
* @param {function} next Next callback
*/
H5P.DragNBar.prototype.confirmPasteError = function (message, top, next) {
// Confirm changing library
var confirmReplace = new H5P.ConfirmationDialog({
headerText: H5PEditor.t('core', 'pasteError'),
dialogText: message,
cancelText: ' ',
confirmText: H5PEditor.t('core', 'ok')
}).appendTo(document.body);
confirmReplace.on('confirmed', next);
confirmReplace.show(top);
};
/**
* Handle keypress events for the entire frame
*/
H5P.DragNBar.keypressHandler = function (event) {
var self = event.data.instance;
if (event.which === BACKSPACE && self.focusedElement && self.$container.is(':visible') && document.activeElement.tagName.toLowerCase() !== 'input') {
event.preventDefault(); // Prevent browser navigating back
}
};
/**
* Handle keyup events for the entire frame
*/
H5P.DragNBar.keyupHandler = function (event) {
var self = event.data.instance;
if (event.which === CTRL) {
// Update key state
ctrlDown = false;
// Enable snapping
self.dnd.snap = 10;
}
if (event.which === SHIFT) {
snapAmount = 1;
}
if (self.focusedElement && (event.which === LEFT || event.which === UP || event.which === RIGHT || event.which === DOWN)) {
// Store position of element after moving
var position = self.getElementSizeNPosition();
self.stopMoving(Math.round(position.left), Math.round(position.top));
}
};
/**
* Handle click events for the entire frame
*/
H5P.DragNBar.clickHandler = function (event) {
var self = event.data.instance;
// Remove pressed on click
delete self.pressed;
};
/**
* Initialize click listeners
*/
H5P.DragNBar.prototype.initClickListeners = function () {
var self = this;
var index = self.instanceIndex;
// Register event listeners
var eventData = {
instance: self
};
H5P.$body.on('keydown.dnb' + index, eventData, H5P.DragNBar.keydownHandler)
.on('keypress.dnb' + index, eventData, H5P.DragNBar.keypressHandler)
.on('keyup.dnb' + index, eventData, H5P.DragNBar.keyupHandler)
.on('click.dnb' + index, eventData, H5P.DragNBar.clickHandler);
// Set blur handler element if option has been specified
var $blurHandlers = this.$container;
if (this.$blurHandlers) {
$blurHandlers = this.$blurHandlers;
}
function handleBlur() {
// Remove coordinates picker if we didn't press an element.
if (self.pressed !== undefined) {
delete self.pressed;
}
else {
self.blurAll();
if (self.focusedElement !== undefined) {
delete self.focusedElement;
}
}
}
$blurHandlers
.keydown(function (e) {
if (e.which === 9) { // pressed tab
handleBlur();
}
})
.click(handleBlur);
};
/**
* Update file URLs. Useful when copying between different contents.
*
* @param {object} params Reference
* @param {function} handler Modifies the path to work when pasted
*/
H5P.DragNBar.updateFileUrls = function (params, handler) {
for (var prop in params) {
if (params.hasOwnProperty(prop) && params[prop] instanceof Object) {
var obj = params[prop];
if (obj.path !== undefined && obj.mime !== undefined) {
obj.path = handler(obj.path);
}
else {
H5P.DragNBar.updateFileUrls(obj, handler);
}
}
}
};
/**
* Attaches the menu bar to the given wrapper.
*
* @param {jQuery} $wrapper
* @returns {undefined}
*/
H5P.DragNBar.prototype.attach = function ($wrapper) {
var self = this;
$wrapper.html('');
$wrapper.addClass('h5peditor-dragnbar');
var $list = H5P.jQuery('
').appendTo($wrapper);
this.$list = $list;
for (var i = 0; i < this.buttons.length; i++) {
var button = this.buttons[i];
if (i === this.overflowThreshold) {
const $buttonMore = H5P.jQuery('
');
$list = $buttonMore
.appendTo($list)
.click(function (e) {
$list.stop().slideToggle(300);
e.preventDefault();
})
.children(':first')
.next();
// Close "more" on click somewhere else
H5P.jQuery(document).click(function (event) {
if (!H5P.jQuery(event.target).is($buttonMore.find('.h5p-dragnbar-more-button')) && $list.css('display') !== 'none') {
$list.stop().slideToggle(300);
}
});
}
this.addButton(button, $list);
}
if (this.enableCopyPaste) {
// Paste button
this.$pasteButton = H5P.jQuery(
'
' +
' ' +
' '
);
H5P.jQuery('
', {
'class': 'h5p-dragnbar-tooltip',
'text': H5PEditor.t('H5P.DragNBar', 'paste')
}).appendTo(this.$pasteButton);
this.$pasteButton.find('.h5p-dragnbar-paste-button').click(function (event) {
event.preventDefault(); // Avoid anchor click making window scroll
self.pasteHandler();
});
if (this.buttons.length > this.overflowThreshold) {
this.$pasteButton.insertAfter($list.parent());
}
else {
this.$pasteButton.appendTo($list);
}
}
this.containTooltips();
};
/**
* Add button.
*
* @param {type} button
* @param {Function} button.createElement Function for creating element
* @param {type} $list
* @returns {undefined}
*/
H5P.DragNBar.prototype.addButton = function (button, $list) {
var that = this;
const hasTitle = (button.title && button.title !== '');
const ariaLabel = hasTitle ? ' aria-label="' + button.title + '"' : '';
var $button = H5P.jQuery(
'' +
' ' +
' '
).appendTo($list);
// Prevent empty tooltips (would show on Firefox)
if (hasTitle) {
H5P.jQuery(' ', {
'class': 'h5p-dragnbar-tooltip',
'text': button.title
}).appendTo($button);
}
let $buttonGroup;
if (button.type === 'group') {
// Create dropdown button group
$buttonGroup = this.addButtonGroup(button.buttons, $button, {title: button.titleGroup});
$buttonGroup.addClass('h5peditor-dragnbar-gone');
// Close group on click somewhere else
H5P.jQuery(document).click(function (event) {
const hitButton = H5P.jQuery(event.target).is($button); // Closing handled by button itself
const hitButtonGroup = H5P.jQuery(event.target).closest('.h5p-dragnbar-button-group').length === 1;
if (!hitButton && !hitButtonGroup) {
$buttonGroup.toggleClass('h5peditor-dragnbar-gone', true);
$button.find('.h5p-dragnbar-tooltip').toggleClass('h5peditor-dragnbar-gone', false);
}
});
}
$button
.hover(function () {
that.containTooltips();
})
.children()
.click(function () {
return false;
}).mousedown(function (event) {
if (event.which !== 1 || !that.enabled) {
return;
}
// Switch between normal button and dropdown button group
if (button.type === 'group') {
if ($buttonGroup !== undefined) {
// Set position here, because content types might add buttons out of order
const offset = parseFloat($button.closest('.h5p-dragnbar').css('padding-left'));
const position = $button.position().left - $buttonGroup.position().left - offset;
if (position > 0) {
$buttonGroup.css('left', position);
}
// Show dropdown and hide buttons tooltip
$buttonGroup.toggleClass('h5peditor-dragnbar-gone');
$button.find('.h5p-dragnbar-tooltip').toggleClass('h5peditor-dragnbar-gone');
}
}
else {
that.newElement = true;
that.pressed = true;
var createdElement = button.createElement();
that.$element = createdElement;
that.$container.css('overflow', 'visible');
// y = 0 will make sure this press is regarded as outside of canvas to place element correctly
that.dnd.press(that.$element, event.pageX, 0);
that.focus(that.$element);
}
});
};
/**
* Contain tooltips.
*
* @returns {undefined}
*/
H5P.DragNBar.prototype.containTooltips = function () {
var that = this;
var containerWidth = that.$container.outerWidth();
this.$list.find('.h5p-dragnbar-tooltip').each(function () {
// Get correct offset even if element is a child
var width = H5P.jQuery(this).outerWidth();
var parentWidth = H5P.jQuery(this).parents('.h5p-dragnbar-li').last().outerWidth();
// Center the tooltip
H5P.jQuery(this).css('left', -(width / 2) + (parentWidth / 2) + 'px');
var offsetLeft = H5P.jQuery(this).position().left += H5P.jQuery(this).parents('.h5p-dragnbar-li').last().position().left;
// If outside left edge
if (offsetLeft <= 0) {
H5P.jQuery(this).css('left', 0);
}
// If outside right edge
if (offsetLeft + width > containerWidth) {
H5P.jQuery(this).css('left', -(width - parentWidth));
}
});
};
/**
* Change container.
*
* @param {jQuery} $container
* @returns {undefined}
*/
H5P.DragNBar.prototype.setContainer = function ($container) {
this.$container = $container;
if (this.dnd) {
this.dnd.$container = $container;
}
if (this.dnr) {
this.dnr.$container = $container;
}
};
/**
* Handler for when the dragging stops. Makes sure the element is inside its container.
*
* @param {Number} left
* @param {Number} top
* @returns {undefined}
*/
H5P.DragNBar.prototype.stopMoving = function (left, top) {
// Calculate percentage
top = top / (this.$container.height() / 100);
left = left / (this.$container.width() / 100);
this.$element.css({top: top + '%', left: left + '%'});
// Give others the result
if (this.stopMovingCallback !== undefined) {
this.stopMovingCallback(left, top);
}
};
/**
* @typedef SizeNPosition
* @type Object
* @property {number} width Outer width of the element
* @property {number} height Outer height of the element
* @property {number} left The X Coordinate
* @property {number} top The Y Coordinate
* @property {number} containerWidth Inner width of the container
* @property {number} containerHeight Inner height of the container
*/
/**
*
* Only works when element is inside this.$container. This is assumed and no
* are done.
*
* @param {H5P.jQuery} [$element] Defaults to focused element.
* @throws 'No element given' if $element is missing
* @return {SizeNPosition}
*/
H5P.DragNBar.prototype.getElementSizeNPosition = function ($element) {
$element = $element || this.focusedElement.$element;
if (!$element || !$element.length) {
throw 'No element given';
}
// Always use outer size for element
var size = $element[0].getBoundingClientRect();
// Always use position relative to container for element
var position = window.getComputedStyle($element[0]);
// We include container inner size as well
var containerSize = window.getComputedStyle(this.$container[0]);
// Start preparing return value
var sizeNPosition = {
width: parseFloat(size.width),
height: parseFloat(size.height),
left: parseFloat(position.left),
top: parseFloat(position.top),
containerWidth: parseFloat(containerSize.width),
containerHeight: parseFloat(containerSize.height)
};
if (position.left.substr(-1, 1) === '%' || position.top.substr(-1, 1) === '%') {
// Some browsers(Safari) gets percentage value instead of pixel value.
// Container inner size must be used to calculate such values.
sizeNPosition.left *= (sizeNPosition.containerWidth / 100);
sizeNPosition.top *= (sizeNPosition.containerHeight / 100);
}
return sizeNPosition;
};
/**
* Makes it possible to move dnb elements by adding to it's x and y
*
* @param {number} x Amount to move on x-axis.
* @param {number} y Amount to move on y-axis.
*/
H5P.DragNBar.prototype.moveWithKeys = function (x, y) {
/**
* Ensure that the given value is within the given boundaries.
*
* @private
* @param {number} value
* @param {number} min
* @param {number} max
* @return {number}
*/
var withinBoundaries = function (value, min, max) {
if (value < min) {
value = min;
}
if (value > max) {
value = max;
}
return value;
};
// Get size and position of current elemet in focus
var sizeNPosition = this.getElementSizeNPosition();
// Change position
sizeNPosition.left += x;
sizeNPosition.top += y;
// Check that values are within boundaries
sizeNPosition.left = withinBoundaries(sizeNPosition.left, 0, sizeNPosition.containerWidth - sizeNPosition.width);
sizeNPosition.top = withinBoundaries(sizeNPosition.top, 0, sizeNPosition.containerHeight - sizeNPosition.height);
// Determine new position style
this.$element.css({
left: sizeNPosition.left + 'px',
top: sizeNPosition.top + 'px',
});
this.dnd.trigger('showTransformPanel');
// Update position of context menu
this.updateCoordinates(sizeNPosition.left, sizeNPosition.top, sizeNPosition.left, sizeNPosition.top);
};
/**
* Makes it possible to focus and move the element around.
* Must be inside $container.
*
* @param {H5P.jQuery} $element
* @param {Object} [options]
* @param {H5P.DragNBarElement} [options.dnbElement] Register new element with dnbelement
* @param {boolean} [options.disableResize] Resize disabled
* @param {boolean} [options.lock] Lock ratio during resize
* @param {string} [clipboardData]
* @returns {H5P.DragNBarElement} Reference to added dnbelement
*/
H5P.DragNBar.prototype.add = function ($element, clipboardData, options) {
var self = this;
options = options || {};
if (this.isEditor && !options.disableResize) {
this.dnr.add($element, options);
}
var newElement = null;
// Check if element already exist
if (options.dnbElement) {
// Set element as added element
options.dnbElement.setElement($element);
newElement = options.dnbElement;
}
else {
options.element = $element;
options.disableCopy = !this.enableCopyPaste;
newElement = new H5P.DragNBarElement(this, clipboardData, options);
this.elements.push(newElement);
}
$element.addClass('h5p-dragnbar-element');
if (this.isEditor) {
if (newElement.contextMenu) {
newElement.contextMenu.on('contextMenuCopy', function () {
self.copyHandler();
});
}
if ($element.attr('tabindex') === undefined) {
// Make it possible to tab between elements.
$element.attr('tabindex', '0');
}
$element.mousedown(function (event) {
if (event.which !== 1) {
return;
}
self.pressed = true;
self.focus($element);
if (self.dnr.active !== true) { // Moving can be stopped if the mousedown is doing something else
self.dnd.press($element, event.pageX, event.pageY);
}
});
}
$element.focus(function () {
self.focus($element);
});
return newElement;
};
/**
* Remove given element in the UI.
*
* @param {H5P.DragNBarElement} dnbElement
*/
H5P.DragNBar.prototype.removeElement = function (dnbElement) {
dnbElement.removeElement();
};
/**
* Select the given element in the UI.
*
* @param {jQuery} $element
* @returns {undefined}
*/
H5P.DragNBar.prototype.focus = function ($element) {
var self = this;
// Blur last focused
if (this.focusedElement && this.focusedElement.$element !== $element) {
this.focusedElement.blur();
this.focusedElement.hideContextMenu();
}
if (!$element.is(':visible')) {
return; // Do not focus invisible items (fixes FF refocus issue)
}
// Keep track of the element we have in focus
self.$element = $element;
this.dnd.setElement($element);
// Show and update coordinates picker
this.focusedElement = this.getDragNBarElement($element);
if (this.focusedElement) {
this.focusedElement.showContextMenu();
this.focusedElement.focus();
self.updateCoordinates();
}
// Wait for potential recreation of element
setTimeout(function () {
self.updateCoordinates();
if (self.focusedElement && self.focusedElement.contextMenu && self.focusedElement.contextMenu.canResize) {
self.focusedElement.contextMenu.updateDimensions();
}
}, 0);
};
/**
* Get dnbElement from $element
* @param {jQuery} $element
* @returns {H5P.DragNBarElement} dnbElement with matching $element
*/
H5P.DragNBar.prototype.getDragNBarElement = function ($element) {
var foundElement;
// Find object with matching element
this.elements.forEach(function (element) {
if (element.getElement().is($element)) {
foundElement = element;
}
});
return foundElement;
};
/**
* Deselect all elements in the UI.
*
* @returns {undefined}
*/
H5P.DragNBar.prototype.blurAll = function () {
this.elements.forEach(function (element) {
element.blur();
});
delete this.focusedElement;
};
/**
* Resize DnB, make sure context menu is positioned correctly.
*/
H5P.DragNBar.prototype.resize = function () {
var self = this;
this.updateCoordinates();
if (self.focusedElement) {
self.focusedElement.resizeContextMenu(self.$element.offset().left - self.$element.parent().offset().left);
}
};
/**
* Update the coordinates of context menu.
*
* @param {Number} [left]
* @param {Number} [top]
* @param {Number} [x]
* @param {Number} [y]
* @returns {undefined}
*/
H5P.DragNBar.prototype.updateCoordinates = function (left, top, x, y) {
if (!this.focusedElement) {
return;
}
var containerPosition = this.$container.position();
if (left && top && x && y) {
left = x + containerPosition.left;
top = y + containerPosition.top;
this.focusedElement.updateCoordinates(left, top, x, y);
}
else {
var position = this.$element.position();
this.focusedElement.updateCoordinates(position.left + containerPosition.left, position.top + containerPosition.top, position.left, position.top);
}
};
/**
* Creates element data to store in the clipboard.
*
* @param {string} from Source of the element
* @param {object} params Element options
* @param {string} [generic] Which part of the parameters can be used by other libraries
* @returns {string} JSON
*/
H5P.DragNBar.clipboardify = function (from, params, generic) {
var clipboardData = {
from: from,
specific: params
};
if (H5PEditor.contentId) {
clipboardData.contentId = H5PEditor.contentId;
}
// Add the generic part
if (params[generic]) {
clipboardData.generic = generic;
}
return clipboardData;
};
/**
* Make sure the given element is inside the container.
*
* @param {SizeNPosition} sizeNPosition For the element
* @returns {SizeNPosition} Only the properties which require change
*/
H5P.DragNBar.fitElementInside = function (sizeNPosition) {
var style = {};
if (sizeNPosition.left < 0) {
// Element sticks out of the left side
style.left = sizeNPosition.left = 0;
}
if (sizeNPosition.width + sizeNPosition.left > sizeNPosition.containerWidth) {
// Element sticks out of the right side
style.left = sizeNPosition.containerWidth - sizeNPosition.width;
if (style.left < 0) {
// Element is wider than the container
style.left = 0;
style.width = sizeNPosition.containerWidth;
}
}
if (sizeNPosition.top < 0) {
// Element sticks out of the top side
style.top = sizeNPosition.top = 0;
}
if (sizeNPosition.height + sizeNPosition.top > sizeNPosition.containerHeight) {
// Element sticks out of the bottom side
style.top = sizeNPosition.containerHeight - sizeNPosition.height;
if (style.top < 0) {
// Element is higher than the container
style.top = 0;
style.height = sizeNPosition.containerHeight;
}
}
return style;
};
/**
* Clean up any event listeners
*/
H5P.DragNBar.prototype.remove = function () {
var index = this.instanceIndex;
H5P.$body.off('keydown.dnb' + index, H5P.DragNBar.keydownHandler)
.off('keypress.dnb' + index, H5P.DragNBar.keypressHandler)
.off('keyup.dnb' + index, H5P.DragNBar.keyupHandler)
.off('click.dnb' + index, H5P.DragNBar.clickHandler);
};
/**
* Toggle dragging on/off.
* When off can not start dragging in any new elements until turned on.
*/
H5P.DragNBar.prototype.toggleDrag = function (enabled = true) {
if (enabled === undefined) {
this.enabled = !this.enabled;
}
else {
this.enabled = enabled;
}
};
/*global H5P*/
/**
* Create context menu
*/
H5P.DragNBarContextMenu = (function ($, EventDispatcher) {
/**
* Constructor for context menu
* @class
* @param {jQuery} $container Parent container
* @param {H5P.DragNBarElement} DragNBarElement
* @param {boolean} [hasCoordinates] Decides if coordinates will be displayed
* @param {boolean} [disableResize] No input for dimensions
* @param {boolean} [disableCopy] Disable copy button
* @param {'vertical' | 'horizontal'} [directionLock] Which way to lock resizing
*/
function ContextMenu($container, DragNBarElement, hasCoordinates, disableResize, disableCopy, directionLock) {
var self = this;
EventDispatcher.call(this);
/**
* Keeps track of DragNBar object
*
* @type {H5P.DragNBar}
*/
this.dnb = DragNBarElement.dnb;
this.directionLock = directionLock;
/**
* Keeps track of DnBElement object
*
* @type {H5P.DragNBarElement}
*/
this.dnbElement = DragNBarElement;
/**
* Keeps track of context menu container
*
* @type {H5P.jQuery}
*/
this.$contextMenu = $('', {
'class': 'h5p-dragnbar-context-menu'
});
/**
* Keeps track of buttons container
*
* @type {H5P.jQuery}
*/
this.$buttons = $('
', {
'class': 'h5p-context-menu-buttons'
});
/**
* Keeps track of transform panel
*
* @type {H5P.jQuery}
*/
this.$transformPanel = $('
', {
'class': 'h5p-transform-panel hide'
});
/**
* Keeps track of context menu parent
*
* @type {jQuery}
*/
this.$parent = $container;
/**
* Keeps track of whether the context menu should display coordinates
* @type {Boolean}
*/
this.hasCoordinates = (hasCoordinates !== undefined ? hasCoordinates : true);
/**
* Determines if the dimensions can be changed.
* @type {boolean}
*/
this.canResize = !disableResize;
/**
* Determines if the transform panel is showing.
* @type {boolean}
*/
this.showingTransformPanel = false;
/**
* Button containing button name and event name that will be fired.
* @typedef {Object} ContextMenuButton
* @property {String} name Machine readable
* @property {String} label Human readable
*/
/**
* Keeps track of button objects
* @type {ContextMenuButton[]}
*/
this.buttons = [
{name: 'Edit', label: H5PEditor.t('H5P.DragNBar', 'editLabel')},
{name: 'BringToFront', label: H5PEditor.t('H5P.DragNBar', 'bringToFrontLabel')},
{name: 'SendToBack', label: H5PEditor.t('H5P.DragNBar', 'sendToBackLabel')},
{name: 'Remove', label: H5PEditor.t('H5P.DragNBar', 'removeLabel')}
];
if (!disableCopy) {
this.buttons.splice(1, 0, {name: 'Copy', label: H5PEditor.t('H5P.DragNBar', 'copyLabel')});
}
/**
* Register transform listener
*
* @param {event} [e] event
* @param {Object} [e.data] event data
* @param {Boolean} [e.data.showTransformPanel] Show transform panel
*/
self.on('contextMenuTransform', function (e) {
if (e && e.data.showTransformPanel !== undefined) {
// Use event data
self.showingTransformPanel = e.data.showTransformPanel;
}
else {
// Toggle showing panel
self.showingTransformPanel = !self.showingTransformPanel;
}
// Toggle sticky transform panel
if (e.data.button === 'Transform') {
if (self.dnb.transformButtonActive) {
self.dnb.transformButtonActive = false;
}
else {
self.dnb.transformButtonActive = true;
}
}
// Remove sticky transform panel when focus is lost
if (e.data.showTransformPanel == false) {
self.dnb.transformButtonActive = false;
}
// Toggle buttons bar and transform panel
self.toggleButtonsBar(!self.showingTransformPanel);
self.toggleTransformPanel(self.showingTransformPanel);
self.$transformButtonWrapper.toggleClass('active', self.showingTransformPanel);
// Realign context menu
self.dnb.updateCoordinates();
});
this.updateContextMenu();
}
// Inherit event dispatcher
ContextMenu.prototype = Object.create(EventDispatcher.prototype);
ContextMenu.prototype.constructor = ContextMenu;
/**
* Create coordinates in context menu
*/
ContextMenu.prototype.addCoordinates = function () {
// Coordinates disabled or exists
if (!this.hasCoordinates || this.$coordinates) {
return;
}
var self = this;
// Add coordinates picker
this.$coordinates = $(
'
' +
'
' + H5PEditor.t('H5P.DragNBar', 'positionLabel') + '
' +
'
' +
' ' +
'
' +
'
, ' +
'
' +
' ' +
'
' +
'
'
).mousedown(function () {
self.dnb.pressed = true;
}).appendTo(this.$transformPanel);
this.$x = this.$coordinates.find('.h5p-dragnbar-x');
this.$y = this.$coordinates.find('.h5p-dragnbar-y');
this.$x.add(this.$y).on('change keydown', function (event) {
if (event.type === 'change' || event.which === 13) {
// Get input
var x = Number(self.$x.val());
var y = Number(self.$y.val());
if (!isNaN(x) && !isNaN(y)) {
// Do not move outside of container
var min = {x: 0 , y: 0};
var max = {
x: self.dnb.$container.width() - self.dnbElement.getElement().outerWidth(),
y: self.dnb.$container.height() - self.dnbElement.getElement().outerHeight()
};
// Check min values
if (x < 0) {
x = min.x;
}
if (y < 0) {
y = min.y;
}
// Check max values
if (x > max.x) {
x = max.x;
}
if (y > max.y) {
y = max.y;
}
// Update and store location
self.dnb.stopMoving(x, y);
if (event.which === 13) {
// Pressed enter, mark number for easy edit
setTimeout(function () {
event.target.focus();
event.target.setSelectionRange(0, event.target.value.length);
}, 0);
}
// Update context menu position
self.dnb.updateCoordinates();
}
}
}).click(function (event) {
// Select coordinates numbers for easy edit
event.target.focus();
event.target.setSelectionRange(0, event.target.value.length);
});
};
/**
* Update the coordinates picker.
*
* @param {Number} left Left pos of context menu
* @param {Number} top Top pos of context menu
* @param {Number} x X value in coordinates
* @param {Number} y Y value in coordinates
*/
ContextMenu.prototype.updateCoordinates = function (left, top, x, y) {
// Move it
this.$contextMenu.css({
left: left,
top: top
});
// Set pos
if (this.hasCoordinates) {
this.$x.val(Math.round(x));
this.$y.val(Math.round(y));
}
};
/**
* Create coordinates in context menu
*/
ContextMenu.prototype.addDimensions = function () {
var self = this;
self.$dimensions = $('
', {
'class': 'h5p-dragnbar-dimensions'
});
// Add label
$('
', {
'class': 'h5p-dragnbar-label',
appendTo: self.$dimensions,
text: H5PEditor.t('H5P.DragNBar', 'sizeLabel')
});
var updateDimensions = function (type) {
var target = parseFloat(this.value);
if (isNaN(target)) {
return;
}
// Get element
var $element = self.dnbElement.getElement();
// Determine min&max values
var min = H5P.DragNResize.MIN_SIZE;
var containerSize = parseFloat(window.getComputedStyle(self.dnb.$container[0])[type]);
var elementStyle = window.getComputedStyle($element[0]);
var max = containerSize - parseFloat(elementStyle[type === 'width' ? 'left' : 'top']);
if (target < min) {
target = min;
}
if (target > max) {
target = max;
}
// Set input field value
self['$' + type].val(Math.round(target));
// Remove any height padding before updating element
var padding = $element[0].getBoundingClientRect()[type] - parseFloat(elementStyle[type]);
target -= padding;
$element.css(type, (target / (containerSize / 100)) + '%');
var eventData = {};
eventData[type] = target / self.dnb.dnr.containerEm;
self.dnb.dnr.trigger('stoppedResizing', eventData);
};
// Add input for width
self.$width = self.getNewInput('width', H5PEditor.t('H5P.DragNBar', 'widthLabel'), self.$dimensions, updateDimensions, self.directionLock === 'vertical');
$('
', {
'class': 'h5p-dragnbar-dimensions-separator',
text: '×',
appendTo: self.$dimensions
});
self.$height = self.getNewInput('height', H5PEditor.t('H5P.DragNBar', 'heightLabel'), self.$dimensions, updateDimensions, self.directionLock === 'horizontal');
self.dnb.dnr.on('moveResizing', function () {
self.updateDimensions();
});
self.$dimensions.appendTo(self.$transformPanel);
};
/**
* Add transform functionality
*
* @param [enableTransform]
*/
ContextMenu.prototype.addTransform = function (enableTransform) {
var self = this;
var transformButtonObject = {name: 'Transform', label: H5PEditor.t('H5P.DragNBar', 'transformLabel')};
var $transformButtonWrapper = $('
', {
'class': 'h5p-transform-button-wrapper'
});
// Attach button
if (enableTransform) {
self.createButton(transformButtonObject)
.appendTo($transformButtonWrapper);
}
self.$transformButtonWrapper = $transformButtonWrapper;
return $transformButtonWrapper;
};
/**
* Updates the values in the input fields for width and height.
*/
ContextMenu.prototype.updateDimensions = function () {
var self = this;
var $element = self.dnbElement.getElement();
var elementSize = window.getComputedStyle($element[0]);
// Re-add any padding removed while updating size
var paddingX = $element[0].getBoundingClientRect()['width'] - parseFloat(elementSize['width']);
var paddingY = $element[0].getBoundingClientRect()['height'] - parseFloat(elementSize['height']);
self.$width.val(Math.round(parseFloat(elementSize.width) + paddingX));
self.$height.val(Math.round(parseFloat(elementSize.height) + paddingY));
};
/**
* Creates a new input field for modifying an element property.
*
* @param {string} type
* @param {string} label
* @param {H5P.jQuery} $container
* @param {function} handler
* @param {boolean} disabled
* @returns {H5P.jQuery}
*/
ContextMenu.prototype.getNewInput = function (type, label, $container, handler, disabled) {
// Wrap input element with label (implicit labeling)
var $wrapper = $('
', {
'class': 'h5p-dragnbar-input h5p-dragnbar-' + type,
'aria-label': label,
appendTo: $container
});
// Create input field
var $input = $('
', {
maxLength: 5,
disabled: disabled === true,
on: {
change: function () {
handler.call(this, type);
},
keydown: function (event) {
if (event.which === 13) { // Enter key
handler.call(this, type);
$input.focus().select();
}
else if (event.which === 38 || event.which === 40) { // Up key
// Increase or decrease the number by using the arrows keys
var currentValue = parseFloat($input.val());
if (!isNaN(currentValue)) {
$input.val(currentValue + (event.which === 38 ? 1 : -1));
handler.call(this, type);
}
}
},
keyup: function (event) {
if (event.which === 38 || event.which === 40) { // Up or Down key
$input.select(); // Select again
}
},
click: function () {
$input.select();
}
},
appendTo: $wrapper
});
return $input;
};
/**
* Create button and add it to buttons bar
* @param {object} button
*/
ContextMenu.prototype.addToMenu = function (button) {
var self = this;
self.createButton(button).appendTo(this.$buttons);
};
/**
* Create button
*
* @param button
* @param {string} button.name
* @param {string} button.label
*
* @returns {H5P.jQuery}
*/
ContextMenu.prototype.createButton = function (button) {
var self = this;
var $newButton = $('
', {
'class': 'h5p-dragnbar-context-menu-button ' + button.name.toLowerCase(),
'role': 'button',
'tabindex': 0,
'aria-label': button.label
}).click(function () {
self.dnb.pressed = true;
self.trigger('contextMenu' + button.name, {button: button.name});
}).keydown(function (e) {
var keyPressed = e.which;
// 32 - space
if (keyPressed === 32) {
$(this).click();
}
});
return $newButton;
};
/**
* Remove button from context menu
* @param {String} buttonName
*/
ContextMenu.prototype.removeFromMenu = function (buttonName) {
var $removeButton = this.$buttons.children('.h5p-context-menu-button-' + buttonName);
$removeButton.remove();
};
/**
* Update context menu with current buttons. Useful when having added or removed buttons.
*/
ContextMenu.prototype.updateContextMenu = function () {
var self = this;
// Clear context menu
this.$buttons.children().remove();
// Check if transform button should be enabled
var enableTransform = false;
// Add coordinates
if (this.hasCoordinates) {
this.addCoordinates();
enableTransform = true;
}
// Add dimensions
if (this.canResize) {
this.addDimensions();
enableTransform = true;
}
// Add menu elements
this.buttons.forEach(function (button) {
self.addToMenu(button);
});
// Add transform button
this.addTransform(enableTransform)
.appendTo(this.$contextMenu);
this.$buttons.appendTo(this.$contextMenu);
this.$transformPanel.appendTo(this.$contextMenu);
};
/**
* Add button and update context menu.
* @param {String} name
* @param {String} label
*/
ContextMenu.prototype.addButton = function (name, label) {
this.buttons.push({name:name, label:label});
this.updateContextMenu();
};
/**
* Remove button from context menu
* @param {string} name
*/
ContextMenu.prototype.removeButton = function (name) {
var self = this;
// Check if button exists
self.buttons.forEach(function (button, index) {
if (button.name === name) {
self.buttons.splice(index, 1);
return;
}
});
this.updateContextMenu();
};
/**
* Toggle buttons visibility
*
* @param [showButtons] Show buttons
*/
ContextMenu.prototype.toggleButtonsBar = function (showButtons) {
var self = this;
if (showButtons !== undefined) {
self.$buttons.toggleClass('hide', !showButtons);
}
else {
self.$buttons.toggleClass('hide');
}
};
/**
* Toggle transform panel visibility.
*
* @param [showTransformPanel] Show transform panel
*/
ContextMenu.prototype.toggleTransformPanel = function (showTransformPanel) {
var self = this;
if (showTransformPanel !== undefined) {
self.$transformPanel.toggleClass('hide', !showTransformPanel);
}
else {
self.$transformPanel.toggleClass('hide');
}
};
/**
* Toggle if coordinates should show
* @param {Boolean} [enableCoordinates] Enable coordinates
*/
ContextMenu.prototype.toggleCoordinates = function (enableCoordinates) {
if (enableCoordinates === undefined) {
this.hasCoordinates = !this.hasCoordinates;
}
else {
this.hasCoordinates = !!enableCoordinates;
}
this.updateContextMenu();
};
/**
* Attach context menu to body.
*/
ContextMenu.prototype.attach = function () {
this.$contextMenu.appendTo(this.$parent);
};
/**
* Detach context menu from DOM.
*/
ContextMenu.prototype.detach = function () {
this.$contextMenu.detach();
};
return ContextMenu;
})(H5P.jQuery, H5P.EventDispatcher);
;
/*global H5P*/
H5P.DragNBarDialog = (function ($, EventDispatcher) {
/**
* Controls the dialog in the interactive video.
*
* @class
* @param {H5P.jQuery} $container for dialog
* @param {H5P.jQuery} $videoWrapper needed for positioning of dialog
*/
function Dialog($container, $videoWrapper) {
var KEY_CODE_ESC = 27;
var KEY_CODE_ENTER = 13;
var KEY_CODE_SPACE = 32;
var self = this;
var titleId = 'dialog-title-' + H5P.createUUID();
// Initialize event inheritance
EventDispatcher.call(self);
/**
* Stops propagating an event
*
* @param {Event} event
*/
var stopEventPropagation = function (event) {
// k is used to stop and start an interactive video
if (event.which === 75) {
event.stopPropagation();
}
};
// Create DOM elements for dialog
var $wrapper = $('
', {
'class': 'h5p-dialog-wrapper h5p-ie-transparent-background h5p-hidden',
on: {
click: function () {
if (!self.disableOverlay) {
self.close();
}
},
keyup: stopEventPropagation,
keydown: stopEventPropagation
}
});
var $dialog = $('
', {
'class': 'h5p-dialog h5p-big',
'aria-labelledby': titleId,
on: {
click: function (event) {
event.stopPropagation();
},
keydown: function (event) {
var isClosable = $close.is(':visible');
if (event.which === KEY_CODE_ESC && isClosable) {
self.close();
}
}
}
}).appendTo($wrapper);
// Create title bar
var $titleBar = $('
', {
'class': 'h5p-dialog-titlebar',
appendTo: $dialog
});
var $title = $('
', {
'class': 'h5p-dialog-title',
id: titleId,
appendTo: $titleBar
});
var $close = $('
', {
'role': 'button',
'class': 'h5p-dialog-close',
tabindex: '0',
title: H5P.t('close'),
'aria-label': H5P.t('close'),
on: {
click: function (event) {
if (event.which === 1) {
self.close();
}
},
keypress: function (event) {
if (event.which === KEY_CODE_SPACE || event.which === KEY_CODE_ENTER) {
self.close();
event.preventDefault();
}
}
},
appendTo: $titleBar
});
// Used instead of close
var $customButtons;
// Create inner DOM elements for dialog
var $inner = $('
', {
'class': 'h5p-dialog-inner'
}).appendTo($dialog);
// Add all to DOM
$wrapper.appendTo($container);
/**
* Reset the dialog's positioning
*
* @private
*/
var resetPosition = function () {
// Reset positioning
$dialog.css({
left: '',
top: '',
height: '',
width: '',
fontSize: '',
bottom: ''
});
$inner.css({
width: '',
height: '',
overflow: ''
});
};
/**
* Display overlay.
*
* @private
* @param {function} next callback
*/
var showOverlay = function (next) {
$wrapper.show();
setTimeout(function () {
// Remove class on next tick to ensure css animation
$wrapper.removeClass('h5p-hidden');
if (next) {
next();
}
}, 0);
};
/**
* Close overlay.
*
* @private
* @param {function} next callback
*/
var hideOverlay = function (next) {
$wrapper.addClass('h5p-hidden');
setTimeout(function () {
// Hide when animation is done
$wrapper.hide();
if (next) {
next();
}
}, 200);
};
/**
* Opens a new dialog. Displays the given element.
*
* @param {H5P.jQuery} $element
* @param {string} [title] Label for the dialog
* @param {string} [classes] For styling
* @param {H5P.jQuery} [$buttons] Use custom buttons for dialog
*/
self.open = function ($element, title, classes, $buttons) {
showOverlay();
$inner.children().detach().end().append($element);
// Reset positioning
resetPosition();
$dialog.addClass('h5p-big');
$title.attr('class', 'h5p-dialog-title' + (classes ? ' ' + classes : ''));
// Add label
if (!title) {
title = '';
}
$title.html(title);
// Clean up after previous custom buttons
if ($customButtons) {
$customButtons.remove();
$close.show();
}
// Add new custom buttons
if ($buttons) {
$customButtons = $buttons;
// Hide default close button
$close.hide();
// Add custom buttons
$buttons.appendTo($titleBar);
}
self.resize();
self.trigger('open');
$dialog.one('transitionend', function() {
// Find visible enabled inputs:
var $inputs = $inner.find('input:visible:not(:disabled)');
var $tabbables = $inner.find('[tabindex]');
// Prioritize the focusing of inputs before other elements
if ($inputs.length) {
$inputs.get(0).focus();
}
// If other tabbables exist like h5p-text, focus on them
else if ($tabbables.length) {
$tabbables.get(0).focus();
}
});
};
self.resize = function () {
if (!$dialog.hasClass('h5p-big')) {
return;
}
var fontSize = toNum($inner.css('fontSize'));
var titleBarHeight = ($titleBar.outerHeight() / fontSize);
// Same as height
var maxHeight = $container.height();
// minus dialog margins
maxHeight -= Number($dialog.css('top').replace('px', '')) * 2;
$inner.css({
width: '100%',
maxHeight: ((maxHeight / fontSize) - titleBarHeight) + 'em',
marginTop: titleBarHeight + 'em'
});
$dialog.css({
bottom: 'auto',
maxHeight: ''
});
};
/**
* Adds a name to the dialog for identifying what it contains.
*
* @param {string} machineName Name of library inside dialog.
*/
self.addLibraryClass = function (machineName) {
$dialog.attr('data-lib', machineName);
};
/**
* Toggle class on the dialog Dom element
* @method toggleClass
* @param {String} cls Classname
* @param {Boolean} toggle
*/
self.toggleClass = function (cls, toggle) {
$dialog.toggleClass(cls, toggle);
};
self.isOpen = function () {
return $wrapper.is(':visible');
};
/**
* Reposition the currently open dialog relative to the given button.
*
* @param {H5P.jQuery} $button
* @param {Object} [size] Sets a size for the dialog, useful for images.
* @param {string|boolean} [type] Type of dialog. Possible values are
* 'medium' and 'big'. It also supports an older version of the function,
* i.e: type = true means 'medium'
*/
self.position = function ($button, size, type) {
// Still support old version of this function
if (type === true) {
type = 'medium';
}
resetPosition();
$dialog.removeClass('h5p-big h5p-medium');
var titleBarHeight = Number($inner[0].style.marginTop.replace('em', ''));
// Use a fixed size
if (size) {
var fontSizeRatio = 16 / toNum($container.css('fontSize'));
// Fixed width
if (size.width) {
size.width = (size.width * fontSizeRatio);
$dialog.css('width', size.width + 'em');
}
// Fixed height
if (size.height) {
size.height = (size.height * fontSizeRatio) + titleBarHeight;
$dialog.css('height', size.height + 'em');
$inner.css({
width: 'auto',
overflow: 'hidden'
});
}
}
if (type === 'medium') {
$dialog.addClass('h5p-medium');
}
if (type === 'big') {
$dialog.addClass('h5p-big');
$dialog.addClass('h5p-stretch');
}
var buttonWidth = $button.outerWidth(true);
var buttonPosition = $button.position();
var containerWidth = $container.width();
var containerHeight = $container.height();
// Position dialog horizontally
var left = buttonPosition.left;
var dialogWidth = $dialog.outerWidth(true);
if (type === 'medium' && dialogWidth > containerWidth) {
// If dialog is too big to fit within the container, display as h5p-big instead.
// Only medium dialogs can become big
$dialog.addClass('h5p-big');
return;
}
if (buttonPosition.left > (containerWidth / 2) - (buttonWidth / 2)) {
// Show on left
left -= dialogWidth - buttonWidth;
}
// Make sure the dialog is within the video on the right.
if ((left + dialogWidth) > containerWidth) {
left = containerWidth - dialogWidth;
}
var marginLeft = parseInt($videoWrapper.css('marginLeft'));
if (isNaN(marginLeft)) {
marginLeft = 0;
}
// And finally, make sure we're within bounds on the left hand side too...
if (left < marginLeft) {
left = marginLeft;
}
// Position dialog vertically
var marginTop = parseInt($videoWrapper.css('marginTop'));
if (isNaN(marginTop)) {
marginTop = 0;
}
// Set dialog size for dialogs which aren't stretched
if (type !== 'big') {
var top = (type === 'medium' ? 0 : (buttonPosition.top + marginTop));
var totalHeight = top + $dialog.outerHeight(true);
if (totalHeight > containerHeight) {
top -= totalHeight - containerHeight;
}
var maxHeight = $container.height() - top + $dialog.height() - $dialog.outerHeight(true);
var fontSize = toNum($container.css('fontSize'));
$dialog.css({
top: (top / (containerHeight / 100)) + '%',
left: (left / (containerWidth / 100)) + '%',
width: (window.getComputedStyle($dialog[0]).width / fontSize) + 'em',
maxHeight: (maxHeight / fontSize) + 'em'
});
$inner.css('maxHeight', ((maxHeight - $titleBar.outerHeight(true)) / fontSize) + 'em');
}
};
/**
* Find max available space inside dialog when positioning relative to
* given button.
*
* @param {H5P.jQuery} $button
* @param {Boolean} fullScreen True if dialog fills whole parent
* @returns {Object} Attrs: width, height
*/
self.getMaxSize = function ($button, fullScreen) {
var buttonWidth = $button.outerWidth(true);
var buttonPosition = $button.position();
var containerWidth = $container.width();
var max = {};
max.height = Number($inner.css('maxHeight').replace('px', ''));
// If border, extract that:
max.height -= Number($inner.css('border-width').replace('px', '')) * 2;
if (fullScreen) {
max.width = containerWidth;
}
else {
if (buttonPosition.left > (containerWidth / 2) - (buttonWidth / 2)) {
// Space to the left of the button minus margin
max.width = buttonPosition.left;
}
else {
// Space to the right of the button minus margin
max.width = (containerWidth - buttonPosition.left - buttonWidth);
}
}
// Use em
var fontSize = toNum($container.css('fontSize'));
max.width = (max.width / fontSize) * (fontSize / 16);
max.height = (max.height / fontSize) * (fontSize / 16);
return max;
};
/**
* Scroll to given position in current dialog.
*
* @param {number} to Scroll position
* @param {number} ms Time the animation takes.
*/
self.scroll = function (to, ms) {
$inner.stop().animate({
scrollTop: to
}, ms);
};
/**
* Close the currently open dialog.
*/
self.close = function (closeInstant) {
$wrapper.addClass('h5p-hidden');
if (closeInstant) {
$wrapper.hide();
self.disableOverlay = false;
$close.show();
}
else {
setTimeout(function () {
$wrapper.hide();
self.disableOverlay = false;
$close.show();
}, 201);
}
self.trigger('close');
// Let others reach to the hiding of this dialog
self.trigger('domHidden', {
'$dom': $wrapper,
'key': 'dialogClosed'
}, {'bubbles': true, 'external': true});
};
/**
* Open overlay only.
*/
self.openOverlay = function () {
self.disableOverlay = true;
$dialog.hide();
showOverlay();
};
/**
* Close overlay only.
*/
self.closeOverlay = function () {
$wrapper.addClass('h5p-hidden');
hideOverlay(function () {
$dialog.show();
self.disableOverlay = false;
});
};
/**
* Removes the close button from the current dialog.
*/
self.hideCloseButton = function () {
$close.hide();
};
/**
* Get width of dialog
* @returns {Number} Width of dialog
*/
self.getDialogWidth = function () {
return $dialog.width();
};
/**
* Reset dialog width
*/
self.removeStaticWidth = function () {
$dialog.css('width', '');
};
}
// Extends the event dispatcher
Dialog.prototype = Object.create(EventDispatcher.prototype);
Dialog.prototype.constructor = Dialog;
/**
* Converts css px value to number.
*
* @private
* @param {string} num
* @returns {Number}
*/
var toNum = function (num) {
return Number(num.replace('px',''));
};
return Dialog;
})(H5P.jQuery, H5P.EventDispatcher);
;
/*global H5P*/
/**
* Create Drag N Bar Element. Connects a DragNBar element to a context menu
*/
H5P.DragNBarElement = (function ($, ContextMenu, EventDispatcher) {
/**
* Constructor DragNBarElement
*
* @class
* @param {H5P.DragNBar} dragNBar Parent dragNBar toolbar
* @param {object} [clipboardData]
* @param {Object} [options] Button object that the element is created from
* @param {Boolean} [options.disableContextMenu] Decides if element should have editor functionality
* @param {Function} [options.createElement] Function for creating element from button
* @param {Function} [options.disableCopy] Copy button disabled or enabled?
* @param {boolean} [options.hasCoordinates] Decides if element will display coordinates
* @param {H5P.jQuery} [options.element] Element
*/
function DragNBarElement(dragNBar, clipboardData, options) {
var self = this;
EventDispatcher.call(this);
this.dnb = dragNBar;
this.options = options || {};
if (!this.options.disableContextMenu) {
this.contextMenu = new ContextMenu(this.dnb.$dialogContainer, this, this.options.hasCoordinates, this.options.disableResize, this.options.disableCopy, this.options.directionLock);
}
this.focused = false;
if (this.options.createElement) {
this.$element = this.options.createElement().appendTo(dragNBar.$container);
this.focus();
}
else {
this.$element = this.options.element;
}
// Let dnb know element has been pressed
if (this.$element) {
if (this.dnb.isEditor) {
this.$element.mousedown(function () {
self.dnb.pressed = true;
});
}
// Run custom focus function on element focus
this.$element.focus(function () {
self.focus();
});
}
/**
* Store element paramets in the local storage.
*/
self.toClipboard = function (width, height) {
if (clipboardData && localStorage) {
clipboardData.width = width;
clipboardData.height = height;
H5P.setClipboard(clipboardData);
}
};
}
// Inheritance
DragNBarElement.prototype = Object.create(EventDispatcher.prototype);
DragNBarElement.prototype.constructor = DragNBarElement;
/**
* Add button to context menu.
*
* @param {string} name
* @param {string} label
*/
DragNBarElement.prototype.addButton = function (name, label) {
this.contextMenu.addToMenu({name:name, label:label});
};
/**
* Get element
* @returns {H5P.jQuery}
*/
DragNBarElement.prototype.getElement = function () {
return this.$element;
};
/**
* Set element
* @param {H5P.jQuery} $element
*/
DragNBarElement.prototype.setElement = function ($element) {
var self = this;
this.$element = $element;
// Register custom focus function on new element focus
this.$element.focus(function () {
self.focus();
});
};
/**
* Show context menu
*/
DragNBarElement.prototype.showContextMenu = function () {
if (this.contextMenu) {
this.contextMenu.attach();
}
};
/**
* Hide context menu
*/
DragNBarElement.prototype.hideContextMenu = function () {
if (this.contextMenu) {
this.contextMenu.detach();
}
};
/**
* Update coordinates in context menu to current location
*
* @param {Number} left Left position of context menu
* @param {Number} top Top position of context menu
* @param {Number} x X coordinate of context menu
* @param {Number} y Y coordinate of context menu
*/
DragNBarElement.prototype.updateCoordinates = function (left, top, x, y) {
if (this.contextMenu) {
this.contextMenu.updateCoordinates(left, top, x, y);
this.resizeContextMenu(x);
}
};
/**
* Float context menu left if width exceeds parent container.
*
* @param {Number} [left] Left position of context menu.
*/
DragNBarElement.prototype.resizeContextMenu = function (left) {
if (this.options.disableContextMenu) {
return;
}
// Need to take into account the left padding of the contextmenu's parent
var paddingLeft = Number(this.contextMenu.$parent.css('padding-left').replace('px', ''));
left = (left || this.$element.position().left) + paddingLeft;
var containerWidth = this.dnb.$container.width();
var $cm = this.contextMenu.$contextMenu;
// Measure full outer width
$cm.css({
position: 'absolute',
left: 0
});
var contextMenuWidth = $cm.outerWidth(true);
// Reset to default
$cm.css({
position: '',
left: left
});
var isTooWide = left + contextMenuWidth >= containerWidth;
if (isTooWide) {
var newLeft = left - contextMenuWidth;
this.contextMenu.$contextMenu.css('left', newLeft + 'px');
this.contextMenu.$contextMenu.addClass('left-aligned');
}
else {
this.contextMenu.$contextMenu.removeClass('left-aligned');
}
};
/**
* Blur element and hide context menu.
*/
DragNBarElement.prototype.blur = function () {
if (this.$element) {
this.$element.removeClass('focused');
this.focused = false;
if (!this.options.disableContextMenu) {
// Hide transform panel
this.contextMenu.trigger('contextMenuTransform', {showTransformPanel: false});
}
}
this.hideContextMenu();
};
/**
* Focus element
*/
DragNBarElement.prototype.focus = function () {
this.$element.addClass('focused');
this.focused = true;
if (this.contextMenu) {
this.resizeContextMenu(this.$element.position().left);
}
};
/**
* Remove element and hide context menu
*/
DragNBarElement.prototype.removeElement = function () {
this.$element.detach();
this.hideContextMenu();
};
return DragNBarElement;
})(H5P.jQuery, H5P.DragNBarContextMenu, H5P.EventDispatcher);
;
(function (DragNBar, EventDispatcher) {
/**
* Allows different forms to be places on top of each other instead of
* in a dialog.
*
* @class H5P.DragNBar.FormManager
* @extends H5P.EventDispatcher
* @param {*} parent
* @param {Object} l10n
*/
DragNBar.FormManager = function (parent, l10n, customIconClass) {
/** @alias H5P.DragNBar.FormManager# */
var self = this;
// Initialize event inheritance
EventDispatcher.call(self);
const formTargets = [self];
let head, footer, subForm, titles, handleTransitionend, proceedButton, breadcrumbButton, alwaysShowButtons;
/**
* Initialize the FormManager.
* Create frame breadcrumbs, and fullscreen button.
*
* @private
*/
const initialize = function () {
self.isMainLibrary = !(parent instanceof H5PEditor.Library)
// Locate target container
self.formContainer = (self.isMainLibrary ? parent.$form : parent.$libraryWrapper)[0];
self.formContainer.classList.add('form-manager');
self.formContainer.classList.add('root-form');
head = document.createElement('div');
head.classList.add('form-manager-head');
footer = document.createElement('div');
footer.classList.add('form-manager-head');
footer.classList.add('form-manager-footer');
self.footer = footer;
// Create button to toggle preivous menu on narrow layouts
breadcrumbButton = createButton('breadcrumb-menu', l10n.expandBreadcrumbButtonLabel, self.toggleBreadcrumbMenu);
breadcrumbButton.classList.add('form-manager-disabled');
head.appendChild(breadcrumbButton);
// Create breadcrumb menu to use when the layout is too narrow for the regular breadcrumb
self.formBreadcrumbMenu = document.createElement('div');
self.formBreadcrumbMenu.classList.add('form-manager-breadcrumb-menulist');
head.appendChild(self.formBreadcrumbMenu);
// Create breadcrumb wrapper
self.formBreadcrumb = document.createElement('div');
self.formBreadcrumb.classList.add('form-manager-breadcrumb');
head.appendChild(self.formBreadcrumb);
// Create the first part of the breadcrumb
const titles = createTitles(parent);
titles.breadcrumb.classList.add('form-manager-comein');
self.formBreadcrumb.appendChild(titles.breadcrumb);
self.formBreadcrumbMenu.appendChild(titles.menu);
// Create 'Proceed to save' button
proceedButton = createButton('proceed', H5PEditor.t('core', 'proceedButtonLabel'), function () {
if (manager.exitSemiFullscreen) {
// Trigger semi-fullscreen exit
manager.exitSemiFullscreen();
manager.exitSemiFullscreen = null;
}
});
hideElement(proceedButton);
head.appendChild(proceedButton);
// Create a container for the action buttons
self.formButtons = document.createElement('div');
self.formButtons.classList.add('form-manager-buttons');
self.footerFormButtons = document.createElement('div');
self.footerFormButtons.classList.add('form-manager-buttons');
hideElement(self.footerFormButtons);
hideElement(self.formButtons); // Buttons are hidden by default
footer.appendChild(self.footerFormButtons);
head.appendChild(self.formButtons);
// Create 'Delete' button
self.formButtons.appendChild(createButton('delete', l10n.deleteButtonLabel, function () {
const e = new H5P.Event('formremove');
e.data = formTargets.length;
formTargets[formTargets.length - 1].trigger(e);
if (!e.preventRemove && formTargets.length > 1) {
closeForm();
}
}));
// Create 'Done' button
self.formButtons.appendChild(createButton('done', l10n.doneButtonLabel, function () {
formTargets[formTargets.length - 1].trigger('formdone', formTargets.length);
if (formTargets.length > 1) {
closeForm();
}
}));
// Footer form buttons
self.footerFormButtons.appendChild(createButton('done', l10n.doneButtonLabel, function () {
formTargets[formTargets.length - 1].trigger('formdone', formTargets.length);
if (formTargets.length > 1) {
closeForm();
}
}));
self.footerFormButtons.appendChild(createButton('delete', l10n.deleteButtonLabel, function () {
const e = new H5P.Event('formremove');
e.data = formTargets.length;
formTargets[formTargets.length - 1].trigger(e);
if (!e.preventRemove && formTargets.length > 1) {
closeForm();
}
}));
// Check if we should add the fullscreen button
if (self.isMainLibrary && H5PEditor.semiFullscreen !== undefined) {
// Create and insert fullscreen button into header
const fullscreenButton = createButton('fullscreen', '', function () {
if (manager.exitSemiFullscreen) {
// Trigger semi-fullscreen exit
manager.exitSemiFullscreen();
}
else {
// Trigger semi-fullscreen enter
manager.exitSemiFullscreen = H5PEditor.semiFullscreen([manager.formContainer], function () {
if (!subForm) {
showElement(proceedButton);
}
toggleFullscreenButtonState(fullscreenButton, true);
self.trigger('formentersemifullscreen');
}, function () {
manager.exitSemiFullscreen = null;
if (!subForm) {
hideElement(proceedButton);
}
toggleFullscreenButtonState(fullscreenButton);
self.trigger('formexitsemifullscreen');
});
}
});
toggleFullscreenButtonState(fullscreenButton);
head.appendChild(fullscreenButton);
}
window.addEventListener('resize', self.updateFormResponsiveness);
// Always clean up on remove
self.on('remove', function () {
window.removeEventListener('resize', self.updateFormResponsiveness);
});
const overlay = document.createElement('div');
overlay.classList.add('form-mananger-overlay');
self.formContainer.insertBefore(overlay, self.formContainer.firstChild);
// Insert everything in the top of the form DOM
self.formContainer.insertBefore(head, self.formContainer.firstChild);
hideElement(footer);
self.formContainer.appendChild(manager.footer);
// Always clean up on remove
self.on('validate', function () {
if (parent.metadata && (!parent.metadata.title || !H5P.trim(parent.metadata.title))) {
// We are trying to save the form without a title
self.closeFormUntil(0);
}
});
};
/**
* Helper for creating buttons.
*
* @private
* @param {string} id
* @param {string} text
* @param {function} clickHandler
* @return {Element}
*/
const createButton = function (id, text, clickHandler) {
const button = document.createElement('button');
button.setAttribute('type', 'button');
button.classList.add('form-manager-button');
button.classList.add('form-manager-' + id);
button.setAttribute('aria-label', text);
button.addEventListener('click', clickHandler);
// Create special inner filler to avoid focus from pointer devices.
const content = document.createElement('span');
content.classList.add('form-manager-button-inner');
content.innerText = text
content.tabIndex = -1;
button.appendChild(content);
return button;
};
/**
* Create two titles, one for the breadcrumb and for the expanded
* breadcrumb menu used for narrow layouts.
*
* @private
* @param {H5PEditor.Library} libraryField
* @return {Element[]}
*/
const createTitles = function (libraryField, customTitle, customIconId) {
const library = (libraryField.params && libraryField.params.library) ? libraryField.params.library : (libraryField.currentLibrary ? libraryField.currentLibrary : undefined);
// Create breadcrumb section.
const title = document.createElement('div');
title.classList.add('form-manager-title');
// Create breadcrumb section.
const menuTitle = document.createElement('div');
menuTitle.classList.add('form-manager-menutitle');
menuTitle.tabIndex = '0';
menuTitle.addEventListener('click', function () {
handleBreadcrumbClick.call(title);
});
menuTitle.addEventListener('keypress', function (e) {
handleBreadcrumbKeypress.call(title, e);
});
// For limiting the length of the menu title
const menuTitleText = document.createElement('span');
menuTitleText.classList.add('form-manager-menutitle-text');
menuTitle.appendChild(menuTitleText);
// Create a tooltip that can be display the whole text on hover
const menuTitleTooltip = document.createElement('span');
menuTitleTooltip.classList.add('form-manager-tooltip');
menuTitle.appendChild(menuTitleTooltip);
// Create a text wrapper so we can limit max-width on the text
const textWrapper = document.createElement('span');
textWrapper.classList.add('truncatable-text');
textWrapper.tabIndex = -1;
title.appendChild(textWrapper);
// Create a tooltip that can display the whole text on hover
const tooltip = document.createElement('span');
tooltip.classList.add('form-manager-tooltip');
title.appendChild(tooltip);
/**
* @private
* @param {string} title WARNING: This is Text do not use as HTML.
*/
const setTitle = function (title) {
textWrapper.innerText = menuTitleText.innerText = tooltip.innerText = menuTitleTooltip.innerText = title;
};
/**
* @private
* @return {string} WARNING: This is Text do not use as HTML.
*/
const getTitle = function () {
if (customTitle) {
return customTitle;
}
else if (libraryField.params && libraryField.params.metadata && libraryField.params.metadata.title &&
libraryField.params.metadata.title.substr(0, 8) !== 'Untitled' ||
libraryField.metadata && libraryField.metadata.title &&
libraryField.metadata.title.substr(0, 8) !== 'Untitled') {
return getText(libraryField.metadata ? libraryField.metadata.title : libraryField.params.metadata.title);
}
else {
if (libraryField.$select !== undefined) {
return libraryField.$select.children(':selected').text();
}
else {
return H5PEditor.libraryCache[library].title;
}
}
};
// Set correct starting title
setTitle(getTitle());
/**
* Help listen for title changes after library has been fully loaded
* @private
*/
const listenForTitleChanges = function () {
if (libraryField.metadataForm) {
libraryField.metadataForm.on('titlechange', function (e) {
// Handle changes to the metadata title
setTitle(getTitle());
manager.updateFormResponsiveness();
});
}
if (textWrapper.innerText === 'Loading...') {
// Correct title was not set initally, try again after library load
setTitle(getTitle());
manager.updateFormResponsiveness();
}
};
// Listen for title updates
if (libraryField.metadataForm === undefined && libraryField.change) {
libraryField.change(listenForTitleChanges);
}
else {
listenForTitleChanges();
}
const iconId = customIconId ? customIconId : library.split(' ')[0].split('.')[1].toLowerCase();
title.classList.add('form-manager-icon-' + iconId);
menuTitle.classList.add('form-manager-icon-' + iconId);
if (customIconClass) {
title.classList.add('form-manager-' + customIconClass);
menuTitle.classList.add('form-manager-' + customIconClass);
}
return {
breadcrumb: title,
menu: menuTitle
};
};
/**
* Look through all parent ancestors to see if a manager already exists.
*
* @private
* @param {*} parent
* @return {DragNBar.FormManager}
*/
const findExistingManager = function (parent) {
if (parent instanceof DragNBar.FormManager) {
return parent.getFormManager(); // Found our parent manager
}
if (parent.parent) {
// Looks deeper
return findExistingManager(parent.parent);
}
else {
return self; // Use our self
}
};
/**
* Help hide an element.
*
* @param {Element} element
* @private
*/
const hideElement = function (element) {
// Make sure element is hidden while still retaining its width without
// expanding the container's height. This is due to some editors resizing
// if their container changes size which leads to some funny transitions.
// Also, having invisible height causes resize loops.
element.classList.add('form-manager-hidden');
element.setAttribute('aria-hidden', true);
};
/**
* Help show a hidden element again
*
* @param {Element} element
* @private
*/
const showElement = function (element) {
element.classList.remove('form-manager-hidden');
element.removeAttribute('aria-hidden');
};
/**
* Update fuillscreen button's attributes dependent on fullscreen or not
*
* @private
* @param {Element} element The fullscreen button element
* @param {boolean} isInFullscreen
*/
const toggleFullscreenButtonState = function (element, isInFullscreen) {
if (isInFullscreen) {
// We are entering fullscreen mode
element.setAttribute('aria-label', H5PEditor.t('core', 'exitFullscreenButtonLabel'));
element.classList.add('form-manager-exit');
}
else {
// We are exiting fullscreen mode
element.setAttribute('aria-label', H5PEditor.t('core', 'enterFullscreenButtonLabel'));
element.classList.remove('form-manager-exit');
}
};
/**
* Closes the current form.
*
* @private
*/
const closeForm = function () {
const activeManager = formTargets.pop();
// Close any open CKEditors
if (H5PEditor.Html) {
H5PEditor.Html.removeWysiwyg();
}
// Let everyone know we're closing
activeManager.trigger('formclose');
// Locate open form and remove it from the manager
const activeSubForm = activeManager.popForm();
if (handleTransitionend) {
// Cancel callback for form if not fully opened.
activeSubForm.removeEventListener('transitionend', handleTransitionend);
handleTransitionend = null;
}
// Find last part of breadcrumb and remove it from the manager
const titles = activeManager.popTitles();
// Remove menu title
manager.formBreadcrumbMenu.removeChild(titles.menu);
// The previous breadcrumb must no longer be clickable
const previousBreadcrumb = titles.breadcrumb.previousSibling;
previousBreadcrumb.removeEventListener('click', handleBreadcrumbClick);
previousBreadcrumb.removeEventListener('keypress', handleBreadcrumbKeypress);
previousBreadcrumb.classList.remove('clickable');
previousBreadcrumb.removeAttribute('tabindex');
const headHeight = manager.getFormHeadHeight();
// Freeze container height to avoid jumping while showing elements
manager.formContainer.style.height = (activeSubForm.getBoundingClientRect().height + headHeight) + 'px';
// Make underlay visible again
if (activeSubForm.previousSibling.classList.contains('form-manager-form')) {
// This is not our last sub-form
showElement(activeSubForm.previousSibling);
}
else {
// Show bottom form
for (let i = 1; i < manager.formContainer.children.length - 1; i++) {
showElement(manager.formContainer.children[i]);
}
// No need for the buttons any more
if (!alwaysShowButtons) {
hideElement(manager.formButtons);
manager.formButtons.classList.remove('form-manager-comein');
// Hide footer
manager.footerFormButtons.classList.remove('form-manager-comein');
hideElement(manager.footerFormButtons);
hideElement(manager.footer);
}
manager.formContainer.classList.add('root-form');
}
// Animation fix for fullscreen max-width limit.
activeSubForm.style.marginLeft = window.getComputedStyle(activeSubForm).marginLeft
// Make the sub-form animatable
activeSubForm.classList.add('form-manager-movable');
// Resume natural container height
manager.formContainer.style.height = '';
// Set sub-form height to cover container
activeSubForm.style.height = (manager.formContainer.getBoundingClientRect().height - headHeight) + 'px';
// Clean up when the final transition animation is finished
onlyOnce(activeSubForm, 'transitionend', function () {
// Remove from DOM
manager.formContainer.removeChild(activeSubForm);
});
// Start the animation
activeSubForm.classList.remove('form-manager-slidein');
if (titles.breadcrumb.offsetWidth === 0) {
// Remove last breadcrumb section in case it's hidden
manager.formBreadcrumb.removeChild(titles.breadcrumb);
}
else {
onlyOnce(titles.breadcrumb, 'transitionend', function () {
// Remove last breadcrumb section
manager.formBreadcrumb.removeChild(titles.breadcrumb);
});
// Start the animation
titles.breadcrumb.classList.remove('form-manager-comein');
}
if (!subForm) {
if (proceedButton && manager.exitSemiFullscreen) {
// We are in fullscreen and closing sub-form, show proceed button
showElement(proceedButton);
}
if (breadcrumbButton) {
breadcrumbButton.classList.add('form-manager-disabled');
}
}
if (self.formContainer.classList.contains('mobile-menu-open')) {
self.toggleBreadcrumbMenu();
}
// Scroll parent manager header into view
manager.formButtons.scrollIntoView();
};
/**
* The breadcrumb click handler figures out how many forms to close.
*
* @private
*/
const handleBreadcrumbClick = function () {
for (let i = 0; i < manager.formBreadcrumb.children.length; i++) {
if (manager.formBreadcrumb.children[i] === this) {
manager.closeFormUntil(i);
break;
}
}
};
/**
* The breadcrumb click handler figures out how many forms to close.
*
* @private
*/
const handleBreadcrumbKeypress = function (e) {
if (e.which === 13 || e.which === 32) {
handleBreadcrumbClick.call(this);
}
};
/**
* Close all forms until the given index.
*
* @param {number} index
*/
self.closeFormUntil = function (index) {
while (formTargets.length - 1 !== index) {
formTargets[formTargets.length - 1].trigger('formdone');
closeForm();
}
};
/**
* Retrieve the current form element and remove it from the manager.
*
* @return {Element}
*/
self.popForm = function () {
const sF = subForm;
subForm = null;
return sF;
};
/**
* Retrieve the current title element and remove it from the manager.
*
* @return {Element}
*/
self.popTitles = function () {
const t = titles;
titles = null;
return t;
};
/**
* Retrieve the active manager.
*
* @return {DragNBar.FormManager}
*/
self.getFormManager = function () {
return manager;
};
/**
* Set the form manager to be used for the next button clicks.
*
* @param {DragNBar.FormManager} target
*/
self.addFormTarget = function (target) {
formTargets.push(target);
};
/**
* Create a new sub-form and shows it.
*
* @param {H5PEditor.Library} libraryField
* @param {Element} formElement
*/
self.openForm = function (libraryField, formElement, customClass, customTitle, customIconId) {
if (subForm) {
return; // Prevent opening more than one sub-form at a time per editor.
}
// Tell manager that we should be receiving the next buttons events
manager.formContainer.classList.remove('root-form');
manager.addFormTarget(self);
// Create the new sub-form
subForm = document.createElement('div');
subForm.classList.add('form-manager-form');
subForm.classList.add('form-manager-movable');
if (customClass) {
subForm.classList.add(customClass);
}
subForm.appendChild(formElement);
// Ensure same height as container
subForm.style.height = (manager.formContainer.getBoundingClientRect().height - manager.getFormHeadHeight()) + 'px';
// Insert into DOM
manager.formContainer.appendChild(subForm);
// Make last part of breadcrumb clickable
const lastBreadcrumb = manager.formBreadcrumb.lastChild;
lastBreadcrumb.addEventListener('click', handleBreadcrumbClick);
lastBreadcrumb.addEventListener('keypress', handleBreadcrumbKeypress);
lastBreadcrumb.classList.add('clickable');
lastBreadcrumb.tabIndex = '0';
// Add breadcrumb section
titles = createTitles(libraryField, customTitle, customIconId);
manager.formBreadcrumb.appendChild(titles.breadcrumb);
manager.formBreadcrumbMenu.insertBefore(titles.menu, manager.formBreadcrumbMenu.firstChild);
// Show our buttons
showElement(manager.formButtons);
showElement(manager.footerFormButtons);
showElement(manager.footer);
// Ensure footer is at the bottom of the form
manager.formContainer.appendChild(manager.footer);
// When transition animation is done and the form is fully open...
handleTransitionend = onlyOnce(subForm, 'transitionend', function () {
handleTransitionend = null;
// Hide everything except first, second, last child and footer
for (let i = 2; i < manager.formContainer.children.length - 1; i++) {
const child = manager.formContainer.children[i];
const skipHiding = child === subForm
|| child.classList.contains('sp-container')
|| child.classList.contains('form-manager-footer');
if (!skipHiding) {
hideElement(manager.formContainer.children[i]);
}
}
// Resume natural height
subForm.style.height = '';
subForm.style.marginLeft = '';
subForm.classList.remove('form-manager-movable');
self.trigger('formopened');
});
// Start animation on the next tick
setTimeout(function () {
// Animation fix for fullscreen max-width limit.
subForm.style.marginLeft = (parseFloat(window.getComputedStyle(manager.formContainer.children[manager.formContainer.children.length - 2]).marginLeft) - 20) + 'px';
subForm.classList.add('form-manager-slidein');
titles.breadcrumb.classList.add('form-manager-comein');
manager.formButtons.classList.add('form-manager-comein');
manager.footerFormButtons.classList.add('form-manager-comein');
manager.updateFormResponsiveness();
}, 0);
if (proceedButton && manager.exitSemiFullscreen) {
// We are in fullscreen and opening sub-form, hide Proceed button
hideElement(proceedButton);
}
if (breadcrumbButton) {
breadcrumbButton.classList.remove('form-manager-disabled');
}
};
/**
* Check if the sub-form is fully opened. (animation finished)
*
* @return {boolean}
*/
self.isFormOpen = function () {
return subForm && !handleTransitionend;
};
/**
* Determine the overall height of the form head section.
*
* @return {number}
*/
self.getFormHeadHeight = function () {
return (alwaysShowButtons ? 0 : head.getBoundingClientRect().height);
};
/**
* Toggle the breadcrumb menu.
*/
self.toggleBreadcrumbMenu = function () {
if (self.formContainer.classList.contains('mobile-menu-open')) {
// Close breadcrumb menu
self.formContainer.classList.remove('mobile-menu-open');
breadcrumbButton.children[0].innerText = l10n.expandBreadcrumbButtonLabel;
breadcrumbButton.setAttribute('aria-label', l10n.expandBreadcrumbButtonLabel);
self.formBreadcrumbMenu.classList.remove('form-manager-comein');
}
else {
// Open breadcrumb menu
self.formContainer.classList.add('mobile-menu-open');
breadcrumbButton.children[0].innerText = l10n.collapseBreadcrumbButtonLabel;
breadcrumbButton.setAttribute('aria-label', l10n.collapseBreadcrumbButtonLabel);
self.formBreadcrumbMenu.classList.add('form-manager-comein');
}
};
/**
* Resize form header elements to fit better inside narrow forms.
*/
self.updateFormResponsiveness = function () {
if (head.classList.contains('mobile-view-large')) {
head.classList.remove('mobile-view-large');
}
if (self.formContainer.classList.contains('mobile-view-small')) {
self.formContainer.classList.remove('mobile-view-small');
}
if (head.offsetWidth < 481) {
self.formContainer.classList.add('mobile-view-small');
}
/**
* Enable tooltips where we have text-ellipsis.
*
* @private
* @param {Element} element
*/
const updateActiveTooltips = function (element) {
let tooltipActive;
for (let i = 0; i < element.children.length; i++) {
const breadcrumbTitle = element.children[i];
if (breadcrumbTitle.firstChild.offsetWidth && breadcrumbTitle.firstChild.scrollWidth > breadcrumbTitle.firstChild.offsetWidth + 1) {
breadcrumbTitle.classList.add('form-mananger-tooltip-active');
tooltipActive = true;
}
else {
breadcrumbTitle.classList.remove('form-mananger-tooltip-active');
}
}
return tooltipActive;
};
if (updateActiveTooltips(self.formBreadcrumb)) {
head.classList.add('mobile-view-large');
// Check again since we made buttons smaller
updateActiveTooltips(self.formBreadcrumb)
}
updateActiveTooltips(self.formBreadcrumbMenu);
};
/**
* Keep the buttons visible even though the last sub-form is closed.
*
* @param {Boolean} state
*/
self.setAlwaysShowButtons = function (state) {
alwaysShowButtons = state;
if (alwaysShowButtons) {
// Show our buttons
showElement(manager.formButtons);
manager.formButtons.classList.add('form-manager-comein');
}
};
// Figure out which manager to use.
const manager = findExistingManager(parent);
if (manager === self) {
initialize(); // We are the first of our kind
}
};
DragNBar.FormManager.prototype = Object.create(EventDispatcher.prototype);
DragNBar.FormManager.prototype.constructor = DragNBar.FormManager;
/**
* Help convert any HTML into text.
*
* @param {string} value
* @return {string}
*/
const getText = function (value) {
const textNode = H5PEditor.$.parseHTML(value);
if (textNode !== null) {
return textNode[0].nodeValue;
}
return value;
};
/**
* Help make sure that an event handler is only triggered once.
*
* @private
* @param {Element} element
* @param {string} eventName
* @param {function} handler
* @return {function} Callback in case of manual cancellation
*/
const onlyOnce = function (element, eventName, handler) {
const callback = function () {
// Make sure we're only called once.
element.removeEventListener(eventName, callback);
// Trigger the real handler
handler.apply(this, arguments);
};
element.addEventListener(eventName, callback);
return callback;
};
})(H5P.DragNBar, H5P.EventDispatcher);
;
!function(t){function e(o){if(n[o])return n[o].exports;var i=n[o]={i:o,l:!1,exports:{}};return t[o].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var n={};e.m=t,e.c=n,e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=4)}([function(t,e,n){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=Object.assign||function(t){for(var e=1;e
=e?t.apply(null,o):function(){var t=Array.prototype.slice.call(arguments,0);return n.apply(null,o.concat(t))}}},i=(e.compose=function(){for(var t=arguments.length,e=Array(t),n=0;n2&&void 0!==arguments[2]?arguments[2]:{ctrl:!1,shift:!1};return-1!==e.indexOf(t.which)&&(!(n.ctrl&&!t.ctrlKey)&&!(n.shift&&!t.shiftKey))};e.isSpaceOrEnterKey=function(t){return-1!==[o.ENTER,o.SPACE].indexOf(t.which)},e.onKey=function(t,e,n){t.on("keydown",function(t){for(var o=0;o",{class:"h5p-video-wrapper"}),s.$controls=k("",{role:"toolbar",class:"h5p-controls hidden"}),s.$read=k("
",{"aria-live":"polite",class:"hidden-but-read"}),this.fontSize=16,this.width=640;var l=!1,c=!0,u=!1;if(s.isTask=!1,s.interactions=[],s.options.assets.interactions)for(var d=0;d
0&&s.toggleEndscreen(!0),r){s.video.play();var a=t.override&&t.override.startVideoAt?t.override.startVideoAt:0;s.seek(a)}break;case H5P.Video.PLAYING:c&&(s.addQualityChooser(),s.addPlaybackRateChooser(),s.removeSplash(),s.startUpdatingBufferBar(),s.toggleBookmarksChooser(!1,{firstPlay:c}),s.toggleEndscreensChooser(!1,{firstPlay:c}),c=!1),s.currentState=H5P.Video.PLAYING,s.controls.$play.removeClass("h5p-pause").attr("aria-label",s.l10n.pause),s.controls.$play.is(":focus")&&(s.controls.$play.blur(),s.controls.$play.focus()),s.timeUpdate(s.video.getCurrentTime());break;case H5P.Video.PAUSED:s.currentState=H5P.Video.PAUSED,s.controls.$play.addClass("h5p-pause").attr("aria-label",s.l10n.play),s.focusInteraction?(s.focusInteraction.focusOnFirstTabbableElement(),delete s.focusInteraction):s.controls.$play.is(":focus")&&(s.controls.$play.blur(),s.controls.$play.focus()),s.timeUpdate(s.video.getCurrentTime());break;case H5P.Video.BUFFERING:s.currentState=H5P.Video.BUFFERING,s.removeSplash(),s.startUpdatingBufferBar()}}),s.video.on("qualityChange",function(t){var e=t.data;if(s.controls&&s.controls.$qualityChooser){if("YouTube"===this.getHandlerName()){if(!s.qualities)return;var n=s.qualities.filter(function(e){return e.name===t.data})[0];return void s.controls.$qualityChooser.find("li").attr("data-quality",t.data).html(n.label)}s.controls.$qualityChooser.find("li").attr("aria-checked","false").filter('[data-quality="'+e+'"]').attr("aria-checked","true")}}),s.video.on("playbackRateChange",function(t){var e=t.data;s.controls&&s.controls.$playbackRateChooser&&s.controls.$playbackRateChooser.find("li").attr("aria-checked","false").filter('[playback-rate="'+e+'"]').attr("aria-checked","true")}),s.on("enterFullScreen",function(){var t=this;s.hasFullScreen=!0,s.$container.parent(".h5p-content").css("height","100%"),s.controls.$fullscreen.addClass("h5p-exit").attr("aria-label",s.l10n.exitFullscreen),s.controls.$fullscreen.blur(),s.controls.$fullscreen.focus(),s.resizeInteractions(),setTimeout(function(){void 0!==t.bubbleEndscreen&&t.bubbleEndscreen.update()},225)}),s.on("exitFullScreen",function(){s.$container.hasClass("h5p-standalone")&&s.$container.hasClass("h5p-minimal")&&s.pause(),s.hasFullScreen=!1,s.$container.parent(".h5p-content").css("height",""),s.controls.$fullscreen.removeClass("h5p-exit").attr("aria-label",s.l10n.fullscreen),s.controls.$fullscreen.blur(),s.controls.$fullscreen.focus(),s.resizeInteractions(),s.dnb&&s.dnb.dialog&&!s.hasUncompletedRequiredInteractions()&&s.dnb.dialog.close()}),s.video.on("captions",function(t){s.controls||(s.addControls(),s.trigger("resize")),s.setCaptionTracks(t.data)}),s.accessibility=new v.default(s.l10n)}},s.togglePlayPause=function(){var t=s.isDisabled(s.controls.$play);if(s.controls.$play.hasClass("h5p-pause")&&!t){var e=!screen||Math.min(screen.width,screen.height)<=s.width;!s.hasFullScreen&&e&&s.$container.hasClass("h5p-standalone")&&s.$container.hasClass("h5p-minimal")&&s.toggleFullScreen(),s.video.play(),s.toggleEndscreen(!1),s.closePopupMenus()}else s.video.pause();s.handleAnswered()},s.toggleMute=function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=s.controls.$volume;s.deactivateSound||(e.hasClass("h5p-muted")?(e.removeClass("h5p-muted").attr("aria-label",s.l10n.mute),s.video.unMute()):(e.addClass("h5p-muted").attr("aria-label",s.l10n.unmute),s.video.mute()),t&&(e.blur(),e.focus()))}}Object.defineProperty(e,"__esModule",{value:!0});var r=n(10),s=o(r),a=n(0),l=o(a),c=n(2),u=o(c),d=n(3),h=n(13),p=o(h),f=n(14),v=o(f),m=n(15),b=o(m),g=n(16),y=o(g),k=H5P.jQuery;i.prototype=Object.create(H5P.EventDispatcher.prototype),i.prototype.constructor=i,i.prototype.setCaptionTracks=function(t){var e=this;if(t.unshift(new H5P.Video.LabelValue("Off","off")),e.captionsTrackSelector)return void e.captionsTrackSelector.updateOptions(t);var n=this.editor?void 0:e.options.video.textTracks.defaultTrackLabel,o=t.reduce(function(t,e){return void 0===t&&n&&e.label===n?e:t},void 0),i=o||e.video.getCaptionsTrack();i||(i=t[0]),e.captionsTrackSelector=new s.default("captions",t,i,"menuitemradio",e.l10n,e.contentId),e.controls.$captionsButton=k(e.captionsTrackSelector.control),e.popupMenuButtons.push(e.controls.$captionsButton),k(e.captionsTrackSelector.control).insertAfter(e.controls.$volume),k(e.captionsTrackSelector.popup).css(e.controlsCss).insertAfter(k(e.captionsTrackSelector.control)),e.popupMenuChoosers.push(k(e.captionsTrackSelector.popup)),k(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(){"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)},i.prototype.getCurrentState=function(){var t=this;if(t.video.play){var e={progress:t.video.getCurrentTime(),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 n=0;n ').appendTo(t))},i.prototype.addSplash=function(){var t=this;void 0!==this.editor||this.video.pressToPlay||!this.video.play||this.$splash||(this.$splash=k('
'+this.options.video.startScreenOptions.title+'
").click(function(){t.video.play()}).appendTo(this.$overlay).find(".h5p-interaction-button").click(function(){return!1}).end(),k(".h5p-splash",this.$splash).keydown(function(e){(0,d.isSpaceOrEnterKey)(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"))},i.prototype.getDuration=function(){return void 0===this.duration&&(this.duration=this.video.getDuration()),this.duration},i.prototype.addControls=function(){var t=this,e=this;this.addSplash(),this.attachControls(this.$controls.removeClass("hidden"));var n=this.getDuration(),o=i.humanizeTime(n),r=i.formatTimeForA11y(n,e.l10n);this.controls.$totalTime.find(".human-time").html(o),this.controls.$totalTime.find(".hidden-but-read").html(e.l10n.totalTime+" "+r),this.controls.$slider.slider("option","max",n),this.bookmarkMenuKeyboardControls=new l.default([new u.default]),this.bookmarkMenuKeyboardControls.on("close",function(){return t.toggleBookmarksChooser(!1)}),this.endscreenMenuKeyboardControls=new l.default([new u.default]),this.endscreenMenuKeyboardControls.on("close",function(){return t.toggleEndscreensChooser(!1)}),this.addSliderInteractions(),this.addBookmarks(),this.addEndscreenMarkers(),this.addBubbles(),this.trigger("controls")},i.prototype.loaded=function(){var t=this.getDuration();if(this.oneSecondInPercentage=100/t,t=Math.floor(t),void 0!==this.editor){var e=$("interactions",this.editor.field.fields),n=$("duration",e.field.fields).fields;n[0].max=n[1].max=t,n[0].min=n[1].min=0;for(var o=$("adaptivity",e.field.fields).fields,r=0;r"+this.l10n.summary+"",mainSummary:!0}),this.initInteraction(this.options.assets.interactions.length-1)}this.currentState===i.ATTACHED&&(this.video.pressToPlay||this.addControls(),this.trigger("resize")),this.currentState=i.LOADED},i.prototype.initInteraction=function(t){var e=this,n=e.options.assets.interactions[t];if(e.override){var o={};n.adaptivity&&n.adaptivity.requireCompletion&&(o.enableRetry=!0),H5P.jQuery.extend(n.action.params.behaviour,e.override,o)}var i;void 0!==e.previousState&&void 0!==e.previousState.answers&&null!==e.previousState.answers[t]&&(i=e.previousState.answers[t]);var r=new p.default(n,e,i);return r.on("display",function(t){var n=t.data;n.appendTo(e.$overlay),r.repositionToWrapper(e.$videoWrapper);var o=void 0!==e.video.pressToPlay;w(o?100:null,function(){(e.currentState===H5P.Video.PLAYING||e.currentState===H5P.Video.BUFFERING)&&r.pause()&&(e.focusInteraction||(e.focusInteraction=r),e.video.pause())}),e.seekingTo&&(e.seekingTo=void 0,n.focus()),setTimeout(function(){r.positionLabel(e.$videoWrapper.width())},0),e.toggleEndscreen(!1)}),r.on("hide",function(){e.handleAnswered()}),r.on("xAPI",function(t){var n=t.getVerb();"interacted"===n&&this.setProgress(p.default.PROGRESS_INTERACTED),-1!==k.inArray(n,["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(e.video.getCurrentTime())+"S"}),e.interactions.push(r),r},i.prototype.handleAnswered=function(){var t=this;t.interactions.forEach(function(e){e.getProgress()===p.default.PROGRESS_INTERACTED&&(e.setProgress(p.default.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)))})},i.prototype.getAnsweredTotal=function(){return this.interactions.filter(function(t){return t.getProgress()===p.default.PROGRESS_ANSWERED}).length},i.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)},i.prototype.addSliderInteractions=function(){var t=this,e=this;this.controls.$interactionsContainer.children().remove(),H5P.jQuery.extend([],this.interactions).sort(function(t,e){return t.getDuration().from-e.getDuration().from}).forEach(function(n){var o=n.addDot();e.menuitems.push(o),void 0===e.previousState&&e.interactionsProgress.push(void 0),e.interactionsProgress[e.menuitems.length-1]===p.default.PROGRESS_ANSWERED&&(n.setProgress(e.interactionsProgress[e.menuitems.length-1]),o.addClass("h5p-interaction-answered")),void 0!==o&&(o.appendTo(t.controls.$interactionsContainer),t.preventSkipping||t.interactionKeyboardControls.addElement(o.get(0)))})},i.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()})},i.prototype.displayBookmarks=function(){return this.options.assets.bookmarks&&this.options.assets.bookmarks.length&&!this.preventSkipping},i.prototype.addBookmarks=function(){if(this.bookmarksMap={},void 0!==this.options.assets.bookmarks&&!this.preventSkipping)for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:{keepStopped:!1,firstPlay:!1};if(this.controls.$bookmarksButton){t=void 0===t?!this.controls.$bookmarksChooser.hasClass("h5p-show"):t;var n=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||n)}t?(this.closePopupMenus(this.controls.$bookmarksButton),this.controls.$bookmarksChooser.find('[tabindex="0"]').first().focus(),this.editor&&(this.interruptVideo(),this.updateChooserTime(this.controls.$bookmarksChooser,".h5p-add-bookmark"))):e.firstPlay||(this.editor&&!e.keepStopped&&this.resumeVideo(),this.controls.$bookmarksChooser.hasClass("h5p-show")||this.controls.$bookmarksButton.focus())},i.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 n=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 o=-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:o}).toggleClass("h5p-show",t).toggleClass("h5p-transitioning",t||n)}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())},i.prototype.updateChooserTime=function(t,e){var n=t.find(e);n.html(n.data("default").replace("@timecode",i.humanizeTime(this.video.getCurrentTime())))},i.prototype.interruptVideo=function(){this.currentState===H5P.Video.PLAYING&&(this.interruptedTemporarily=!0,this.video.pause())},i.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()},i.prototype.toggleEndscreen=function(t){!this.editor&&this.hasStar&&t!==this.bubbleEndscreen.isActive()&&(t=void 0===t?!this.bubbleEndscreen.isActive():t,t?(this.disableTabIndexes(".h5p-interactive-video-endscreen"),this.stateBeforeEndscreen=this.currentState,this.video.pause()):(this.restoreTabIndexes(),this.controls.$endscreensButton.focus(),this.stateBeforeEndscreen===H5P.Video.PLAYING&&(this.video.play(),this.stateBeforeEndscreen=void 0)),this.controls.$endscreensButton.toggleClass("h5p-star-active",t),this.bubbleEndscreen.toggle(t,!0))},i.prototype.showPreventSkippingMessage=function(t){var e=this;e.preventSkippingWarningTimeout||(e.$preventSkippingMessage||(e.$preventSkippingMessage=k("",{class:"h5p-prevent-skipping-message",appendTo:e.controls.$bookmarksContainer}),e.$preventSkippingMessage=k("
",{class:"h5p-prevent-skipping-message",appendTo:e.controls.$endscreensContainer}),e.$preventSkippingMessageText=k("
",{class:"h5p-prevent-skipping-message-text",html:e.l10n.navDisabled,appendTo:e.$preventSkippingMessage}),e.$preventSkippingMessageTextA11y=k("
",{class:"hidden-but-read",html:e.l10n.navDisabled,appendTo:e.controls.$slider})),e.$preventSkippingMessage.css("left",t),setTimeout(function(){e.$preventSkippingMessage.addClass("h5p-show").attr("aria-hidden","false")},0),e.preventSkippingWarningTimeout=setTimeout(function(){e.$preventSkippingMessage.removeClass("h5p-show").attr("aria-hidden","true"),setTimeout(function(){e.preventSkippingWarningTimeout=void 0},500)},2e3))},i.prototype.onBookmarkSelect=function(t,e){var n=this;n.currentState!==H5P.Video.PLAYING&&(t.mouseover().mouseout(),setTimeout(function(){n.timeUpdate(n.video.getCurrentTime())},0)),"true"===n.controls.$more.attr("aria-expanded")&&n.$container.hasClass("h5p-minimal")?n.controls.$more.click():n.toggleBookmarksChooser(!1),n.video.play(),n.seek(e.time);var o=i.formatTimeForA11y(e.time,n.l10n);setTimeout(function(){return n.read(n.l10n.currentTime+" "+o)},150)},i.prototype.onEndscreenSelect=function(t,e){var n=this;n.currentState!==H5P.Video.PLAYING&&(t.mouseover().mouseout(),setTimeout(function(){n.timeUpdate(n.video.getCurrentTime())},0)),"true"===n.controls.$more.attr("aria-expanded")&&n.$container.hasClass("h5p-minimal")?n.controls.$more.click():n.toggleEndscreensChooser(!1),n.video.play(),n.seek(e.time);var o=i.formatTimeForA11y(e.time,n.l10n);setTimeout(function(){return n.read(n.l10n.currentTime+" "+o)},150)},i.prototype.addBookmark=function(t,e){var n=this,o=n.options.assets.bookmarks[t];void 0===e&&(e=Math.floor(10*o.time)/10);var i=n.bookmarksMap[e]=k('
").appendTo(n.controls.$bookmarksContainer).data("id",t).hover(function(){void 0!==n.bookmarkTimeout&&clearTimeout(n.bookmarkTimeout),n.controls.$bookmarksContainer.children(".h5p-show").removeClass("h5p-show"),i.addClass("h5p-show")},function(){n.bookmarkTimeout=setTimeout(function(){i.removeClass("h5p-show")},n.editor?1e3:2e3)});i.find(".h5p-bookmark-label").css("maxWidth",parseInt(n.controls.$slider.parent().css("marginRight"))-35),void 0===n.controls.$bookmarksList&&(n.controls.$bookmarksList=k('
').insertAfter(n.controls.$bookmarksChooser.find("h3")));var r=k('
'+o.label+" ").click(function(){return n.onBookmarkSelect(i,o)}).keydown(function(t){(0,d.isSpaceOrEnterKey)(t)&&n.onBookmarkSelect(i,o),t.stopPropagation()});n.bookmarkMenuKeyboardControls.addElement(r.get(0));var s=n.controls.$bookmarksList.children(":eq("+t+")");return 0!==s.length?r.insertBefore(s):r.appendTo(n.controls.$bookmarksList),n.on("bookmarksChanged",function(o){var s=o.data.index,a=o.data.number;s===t&&a<0?(r.remove(),delete n.bookmarksMap[e]):t>=s&&(t+=a,i.data("id",t))}),n.trigger("bookmarkAdded",{bookmark:i}),i},i.prototype.addEndscreen=function(t,e){var n=this,o=n.options.assets.endscreens[t];void 0===e&&(e=Math.floor(10*o.time)/10);var i;if(!this.editor)return n.getDuration()-e<1&&(e=n.getDuration()),void(i=n.endscreensMap[e]=!0);i=n.endscreensMap[e]=k('
").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"),i.addClass("h5p-show")},function(){n.endscreenTimeout=setTimeout(function(){i.removeClass("h5p-show")},n.editor?1e3:2e3)}),n.editor&&(n.endscreenTimeout=setTimeout(function(){i.removeClass("h5p-show")},1e3)),i.find(".h5p-endscreen-label").css("maxWidth",parseInt(n.controls.$slider.parent().css("marginRight"))-35),void 0===n.controls.$endscreensList&&(n.controls.$endscreensList=k('
').insertAfter(n.controls.$endscreensChooser.find("h3")));var r=k('
'+o.label+" ").click(function(){return n.onEndscreenSelect(i,o)}).keydown(function(t){(0,d.isSpaceOrEnterKey)(t)&&n.onEndscreenSelect(i,o),t.stopPropagation()});n.endscreenMenuKeyboardControls.addElement(r.get(0));var s=n.controls.$endscreensList.children(":eq("+t+")");return 0!==s.length?r.insertBefore(s):r.appendTo(n.controls.$endscreensList),n.on("endscreensChanged",function(o){var s=o.data.index,a=o.data.number;s===t&&a<0?(r.remove(),delete n.endscreensMap[e]):t>=s&&(t+=a,i.data("id",t))}),n.trigger("endscreenAdded",{endscreen:i}),i},i.prototype.attachControls=function(t){var e=this,n=k("
",{class:"h5p-controls-left",appendTo:t}),o=k("
",{class:"h5p-control h5p-slider",appendTo:t});e.hasStar&&(e.$star=k("
",{class:"h5p-control h5p-star",appendTo:t}),e.$starBar=k("
",{class:"h5p-control h5p-star h5p-star-bar",appendTo:e.$star}),k("
",{class:"h5p-control h5p-star h5p-star-background",appendTo:e.$star}),e.$starAnimation=k("
",{class:"h5p-control h5p-star h5p-star-animation h5p-star-animation-inactive",appendTo:e.$star}));var r=k("
",{class:"h5p-controls-right",appendTo:t});e.preventSkipping&&e.setDisabled(o),e.controls={},e.controls.$play=e.createButton("play","h5p-control h5p-pause",n,e.togglePlayPause),e.showRewind10&&(e.controls.$rewind10=e.createButton("rewind10","h5p-control",n,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 s=function(){var t=e.$container.hasClass("h5p-minimal")&&"true"===e.controls.$more.attr("aria-expanded");return t&&e.controls.$more.click(),t},a=function(t,n){return function(){var o=e.controls[t],i=e.controls[n],r="true"===o.attr("aria-disabled"),a="true"===o.attr("aria-expanded");r||(a?(o.attr("aria-expanded","false"),i.hasClass("h5p-show")||o.focus(),i.removeClass("h5p-show"),s(),e.resumeVideo()):(o.attr("aria-expanded","true"),i.addClass("h5p-show"),i.find('[tabindex="0"]').focus(),e.closePopupMenus(o)))}},c=e.editor||e.displayBookmarks();if(c&&(e.controls.$bookmarksChooser=H5P.jQuery("
",{class:"h5p-chooser h5p-bookmarks",role:"dialog",html:'"}),e.popupMenuChoosers.push(e.controls.$bookmarksChooser),e.controls.$bookmarksChooser.append(k("
",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return e.toggleBookmarksChooser()},keydown:function(t){(0,d.isSpaceOrEnterKey)(t)&&(e.toggleBookmarksChooser(),t.preventDefault())}})),e.showRewind10&&e.controls.$bookmarksChooser.addClass("h5p-rewind-displacement"),e.controls.$bookmarksButton=e.createButton("bookmarks","h5p-control",n,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 h=e.editor?"star h5p-star-foreground-editor":"star h5p-star-foreground",p=e.editor?function(){return e.toggleEndscreensChooser()}:function(){return e.toggleEndscreen()};e.controls.$endscreensButton=e.createButton(h,"h5p-control",e.$star,p),e.controls.$endscreensButton.attr("aria-label",e.l10n.summary),e.popupMenuButtons.push(e.controls.$endscreensButton)}e.editor&&(e.controls.$endscreensChooser=H5P.jQuery("
",{class:"h5p-chooser h5p-endscreens",role:"dialog",html:'"}),e.popupMenuChoosers.push(e.controls.$endscreensChooser),e.controls.$endscreensChooser.append(k("",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return e.toggleEndscreensChooser()},keydown:function(t){(0,d.isSpaceOrEnterKey)(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 f='\n \n 0:00 \n ',v=k(''+f+"
").appendTo(n);e.controls.$currentTimeSimple=v.find(".human-time"),e.controls.$currentTimeA11ySimple=v.find(".hidden-but-read");var m=i.formatTimeForA11y(0,e.l10n),b=k('\n '+f+'\n / \n \n '+e.l10n.totalTime+" "+m+' \n 0:00 \n \n
').appendTo(r),g=b.find(".h5p-current");e.controls.$currentTime=g.find(".human-time"),e.controls.$currentTimeA11y=g.find(".hidden-but-read"),e.controls.$totalTime=b.find(".h5p-total"),e.updateCurrentTime(0);var y=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",r,function(){"true"===e.controls.$more.attr("aria-expanded")?y():(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:'"}),e.popupMenuChoosers.push(e.controls.$playbackRateChooser);var S=function(){e.isMinimal?e.controls.$more.click():e.controls.$playbackRateButton.click(),e.resumeVideo()};e.controls.$playbackRateChooser.append(k("",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return S()},keydown:function(t){(0,d.isSpaceOrEnterKey)(t)&&(S(),t.preventDefault())}})),e.controls.$playbackRateButton=e.createButton("playbackRate","h5p-control",r,a("$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),function(){return-1!==navigator.userAgent.indexOf("Android")}()||function(){return-1!==navigator.userAgent.indexOf("iPad")}()||(e.controls.$volume=e.createButton("mute","h5p-control",r,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:'"}),e.popupMenuChoosers.push(e.controls.$qualityChooser);var $=function(){e.isMinimal?e.controls.$more.click():e.controls.$qualityButton.click(),e.resumeVideo()};e.controls.$qualityChooser.append(k("",{role:"button",class:"h5p-chooser-close-button",tabindex:"0","aria-label":e.l10n.close,click:function(){return $()},keydown:function(t){(0,d.isSpaceOrEnterKey)(t)&&($(),t.preventDefault())}})),e.controls.$qualityButton=e.createButton("quality","h5p-control",r,a("$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",r,function(){e.toggleFullScreen()})),e.controls.$minimalOverlay=H5P.jQuery("
",{class:"h5p-minimal-overlay",appendTo:e.$container});var w=H5P.jQuery("
",{role:"menu",class:"h5p-minimal-wrap",appendTo:e.controls.$minimalOverlay});e.minimalMenuKeyboardControls=new l.default([new u.default]),e.minimalMenuKeyboardControls.on("close",function(){return y()}),e.controls.$overlayButtons=H5P.jQuery([]),c&&(e.controls.$bookmarkButtonMinimal=e.createButton("bookmarks","h5p-minimal-button",w,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",w,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",w,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 l.default([new u.default]),e.controls.$interactionsContainer=k("
",{role:"menu",class:"h5p-interactions-container","aria-label":e.l10n.interaction}),e.controls.$bookmarksContainer=k("
",{class:"h5p-bookmarks-container",appendTo:o}),e.controls.$endscreensContainer=k("
",{class:"h5p-endscreens-container",appendTo:o}),e.hasPlayPromise=!1,e.hasQueuedPause=!1,e.delayed=!1,e.controls.$slider=k("
",{appendTo:o}).slider({value:0,step:.01,orientation:"horizontal",range:"min",max:0,create:function(t){var n=k(t.target).find(".ui-slider-handle");n.attr("role","slider").attr("aria-valuemin","0").attr("aria-valuemax",e.getDuration().toString()).attr("aria-valuetext",i.formatTimeForA11y(0,e.l10n)).attr("aria-valuenow","0"),e.preventSkipping&&e.setDisabled(n).attr("aria-hidden","true")},start:function(){e.currentState!==i.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=i.SEEKING,e.removeSplash(),e.$overlay.addClass("h5p-visible"))},slide:function(t,n){var o=(0,d.isKey)(t,[d.Keys.ARROW_LEFT,d.Keys.ARROW_RIGHT]),i=n.value;if(o){var r=(0,d.isKey)(t,[d.Keys.ARROW_RIGHT]),s=e.getDuration();i=r?Math.min(i+5,s):Math.max(i-5,0),e.timeUpdate(i,!0)}return e.seek(i),e.updateInteractions(i),e.updateCurrentTime(i),!o},stop:function(t,n){e.currentState=e.lastState,e.seek(n.value),e.recreateCurrentInteractions();var o=e.lastState===H5P.Video.PLAYING||e.lastState===H5P.Video.VIDEO_CUED||e.hasQueuedPause;if(e.hasPlayPromise)e.hasQueuedPause=!1;else if(o){e.hasQueuedPause=!1;var i=e.video.play();e.hasQueuedPause=!1,i&&i.then?(e.hasPlayPromise=!0,i.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))}else e.timeUpdate(n.value);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(o),e.preventSkipping&&(e.controls.$slider.slider("disable"),e.controls.$slider.click(function(t){return e.showPreventSkippingMessage(t.offsetX),!1})),e.displayBookmarks()&&e.showBookmarksmenuOnLoad&&!1===e.video.pressToPlay&&e.toggleBookmarksChooser(!0),e.controls.$buffered=k("
",{class:"h5p-buffered",prependTo:e.controls.$slider})},i.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))},i.prototype.playBubbleAnimation=function(t){this.bubbleScore.setContent(t),this.bubbleScore.animate()},i.prototype.hasActivePauseInteraction=function(){var t=!1;return this.interactions.forEach(function(e){e.getElement()&&e.pause()&&(t=!0)}),t},i.prototype.createButton=function(t,e,n,o,i){var r=this,s={role:"button",tabindex:0,class:(void 0===e?"":e+" ")+"h5p-"+t,on:{click:function(){o.call(this)},keydown:function(t){(0,d.isSpaceOrEnterKey)(t)&&(o.call(this),t.preventDefault(),t.stopPropagation())}},appendTo:n};return s[i?"text":"aria-label"]=r.l10n[t],H5P.jQuery("
",s)},i.prototype.addQualityChooser=function(){var t=this;if(t.qualityMenuKeyboardControls=new l.default([new u.default]),t.qualityMenuKeyboardControls.on("close",function(){return t.controls.$qualityButton.click()}),this.video.getQualities&&(t.qualities=this.video.getQualities(),t.qualities&&void 0!==this.controls.$qualityButton&&t.isDisabled(t.controls.$qualityButton))){var e=this.video.getQuality(),n=t.qualities;"YouTube"===this.video.getHandlerName()&&(n=n.filter(function(t){return t.name===e}));for(var o="",i=0;i'+r.label+""}var a=k('").appendTo(this.controls.$qualityChooser);a.children().click(function(){var e=k(this).attr("data-quality");t.updateQuality(e)}).keydown(function(e){if((0,d.isSpaceOrEnterKey)(e)){var n=k(this).attr("data-quality");t.updateQuality(n)}e.stopPropagation()});a.find("li").get().forEach(function(e){t.qualityMenuKeyboardControls.addElement(e);var n="true"===e.getAttribute("aria-checked");S(e,n)}),t.removeDisabled(this.controls.$qualityButton.add(this.controls.$qualityButtonMinimal))}},i.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())},i.prototype.addPlaybackRateChooser=function(){var t=this,e=this;if(this.playbackRateMenuKeyboardControls=new l.default([new u.default]),this.playbackRateMenuKeyboardControls.on("close",function(){return e.controls.$playbackRateButton.click()}),this.video.getPlaybackRates){var n=this.video.getPlaybackRates();if(!(n.length<2)&&n&&void 0!==this.controls.$playbackRateButton&&e.isDisabled(this.controls.$playbackRateButton)){for(var o=this.video.getPlaybackRate(),i="",r=0;r'+s+""}var a=k('").appendTo(this.controls.$playbackRateChooser);a.children().click(function(){var t=k(this).attr("playback-rate");e.updatePlaybackRate(t)}).keydown(function(t){if((0,d.isSpaceOrEnterKey)(t)){var n=k(this).attr("playback-rate");e.updatePlaybackRate(n)}t.stopPropagation()}),a.find("li").get().forEach(function(e){t.playbackRateMenuKeyboardControls.addElement(e);var n="true"===e.getAttribute("aria-checked");S(e,n)}),e.removeDisabled(this.controls.$playbackRateButton.add(this.controls.$playbackRateButtonMinimal))}}},i.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()},i.prototype.startUpdatingBufferBar=function(){var t=this;if(!t.bufferLoop){!function e(){var n=t.video.getBuffered();n&&t.controls.$buffered&&(t.controls.$buffered.css("width",n+"%"),t.hasStar&&(n>99?t.$starBar.addClass("h5p-star-bar-buffered"):t.$starBar.removeClass("h5p-star-bar-buffered"))),t.bufferLoop=setTimeout(e,500)}()}},i.prototype.resize=function(){if(this.$container){var t=this.$container.hasClass("h5p-fullscreen")||this.$container.hasClass("h5p-semi-fullscreen");this.$videoWrapper.css({marginTop:"",marginLeft:"",width:"",height:""}),this.video.trigger("resize");var e,n,o=this.justVideo?0:this.$controls.height(),i=this.$container.height();if(t){if((n=this.$videoWrapper.height())+o<=i)this.$videoWrapper.css("marginTop",(i-o-n)/2),e=this.$videoWrapper.width(),void 0!==this.bubbleEndscreen&&this.bubbleEndscreen.fullscreen(!0,i,n);else{var r=this.$videoWrapper.width()/n,s=i-o;e=s*r,this.$videoWrapper.css({marginLeft:(this.$container.width()-e)/2,width:e,height:s}),void 0!==this.bubbleEndscreen&&this.bubbleEndscreen.fullscreen(!0)}this.video.trigger("resize")}else e=this.$container.width(),void 0!==this.bubbleEndscreen&&this.bubbleEndscreen.fullscreen();this.scaledFontSize=e>this.width?this.fontSize*(e/this.width):this.fontSize,this.$container.css("fontSize",this.scaledFontSize+"px"),this.editor||(e=0)try{var o=n.controls.$slider.find(".ui-slider-handle"),r=i.formatTimeForA11y(t,n.l10n);n.controls.$slider.slider("option","value",t),o.attr("aria-valuetext",r),o.attr("aria-valuenow",t.toString())}catch(t){return}n.updateInteractions(t),e||setTimeout(function(){(n.currentState===H5P.Video.PLAYING||n.currentState===H5P.Video.BUFFERING&&n.lastState===H5P.Video.PLAYING)&&n.timeUpdate(n.video.getCurrentTime())},40)},i.prototype.updateInteractions=function(t){var e=this,n=Math.floor(10*t)/10;if(n!==e.lastTenth){void 0!==e.bookmarksMap&&void 0!==e.bookmarksMap[n]&&e.bookmarksMap[n].mouseover().mouseout();(function(){return void 0!==e.endscreensMap&&void 0!==e.endscreensMap[n]&&e.currentState!==i.SEEKING})()&&e.getAnsweredTotal()>0&&e.toggleEndscreen(!0)}e.lastTenth=n,e.toggleInteractions(t);var o=Math.floor(t);o!==e.lastSecond&&(e.currentState!==H5P.Video.PLAYING&&e.currentState!==H5P.Video.PAUSED||e.updateCurrentTime(o)),e.lastSecond=o},i.prototype.updateCurrentTime=function(t){var e=this;t=Math.max(t,0);var n=i.humanizeTime(t),o=i.formatTimeForA11y(t,e.l10n);e.controls.$currentTime.html(n),e.controls.$currentTimeA11y.html(e.l10n.currentTime+" "+o),e.controls.$currentTimeSimple.html(n),e.controls.$currentTimeA11ySimple.html(e.l10n.currentTime+" "+o)},i.prototype.getUsersScore=function(){for(var t=0,e=0;e0&&void 0!==arguments[0]?arguments[0]:".h5p-dialog-wrapper",e=this,n=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=k(this),e=k.contains(n.get(0),t.get(0));if(t.data("tabindex"))return!0;if(!e){var o=t.attr("tabindex");return t.data("tabindex",o),t.attr("tabindex","-1"),!0}return!1})},i.prototype.restoreTabIndexes=function(t){var e=this;e.$tabbables&&(e.$tabbables.each(function(){var e=k(this),n=e.data("tabindex");if(t&&!k.contains(t.get(0),e.get(0)))return!0;e.hasClass("ui-slider-handle")?(e.attr("tabindex",0),e.removeData("tabindex")):void 0!==n?(e.attr("tabindex",n),e.removeData("tabindex")):e.removeAttr("tabindex")}),t||(e.$tabbables=void 0))},i.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(n){return t.trapFocusInInteractions(e,k(n.target))}):this.$container.off("focusin","**")},i.prototype.trapFocusInInteractions=function(t,e){var n=t.some(function(t){var n=t.getElement();return P(n,e)}),o=!!this.$mask&&P(this.$mask,e);if(!n&&!o){var i=t[0].getElement();i&&i.focus()}},i.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")},i.prototype.showWarningMask=function(){var t=this,e="interactive-video-"+t.contentId+"-"+t.instanceIndex+"-completion-warning-text";return t.$mask||(t.$mask=k('\n
\n
'+t.l10n.requiresCompletionWarning+'
\n
'+t.l10n.back+" \n
\n
").click(function(){t.$mask.hide()}).appendTo(t.$container)),t.$mask.show(),t.$mask.find(".h5p-button-back").focus(),t.$mask},i.prototype.setDisabled=function(t){return t.attr("aria-disabled","true").attr("tabindex","-1")},i.prototype.isDisabled=function(t){return"true"===t.attr("aria-disabled")},i.prototype.removeDisabled=function(t){return t.removeAttr("aria-disabled").attr("tabindex","0")},i.prototype.hasUncompletedRequiredInteractions=function(t){var e=this;return(void 0!==t?e.getVisibleInteractionsAt(t):e.getVisibleInteractions()).some(function(t){return t.getRequiresCompletion()&&!t.hasFullScore()})},i.prototype.getVisibleInteractions=function(){return this.interactions.filter(function(t){return t.isVisible()})},i.prototype.getVisibleInteractionsAt=function(t){return this.interactions.filter(function(e){return e.visibleAt(t)})},i.prototype.showSolutions=function(){},i.prototype.getTitle=function(){return H5P.createTitle(this.contentData&&this.contentData.metadata&&this.contentData.metadata.title?this.contentData.metadata.title:"Interactive Video")},i.prototype.findNextInteractionToShow=function(t,e){for(var n=void 0,o=0;ot||i.from==t&&(void 0===e||o>e))&&(void 0===n||i.from0?n.hours+" "+e.hours+", ":"")+n.minutes+" "+e.minutes+", "+n.seconds+" "+e.seconds},i.secondsToMinutesAndHours=function(t){var e=Math.floor(t/60);return{seconds:Math.floor(t%60),minutes:e%60,hours:Math.floor(e/60)}};var S=function(t,e){e?t.setAttribute("tabindex","0"):t.removeAttribute("tabindex")},$=function(t,e){for(var n=0;n'+i[t]+""),c.popup.setAttribute("role","dialog");var $=d("h5p-chooser-close-button",l.ICON,i.close,g,"div","button");c.popup.appendChild($),c.control=d("h5p-control h5p-"+t,l.ICON,i[t],g,"div","button"),c.control.setAttribute("aria-haspopup","true"),c.overlayControl=d("h5p-minimal-button h5p-"+t,l.TEXT,i[t],g,"div","menuitem"),c.overlayControl.tabIndex="-1",c.overlayControl.classList.add("h5p-hide"),c.updateOptions(e)};c.prototype=Object.create(H5P.EventDispatcher.prototype),c.prototype.constructor=c;var u=function(t,e,n){var o=document.createElement(n||"div");return t&&(o.className=t),e&&(o.innerHTML=e),o},d=function(t,e,n,o,i,r){var s=u(t,e===l.TEXT?n:"",i);return s.tabIndex=0,s.setAttribute("role",r),e===l.ICON&&(s.title=n),s.addEventListener("click",function(t){o.call(s,t)},!1),s.addEventListener("keydown",function(t){32!==t.which&&13!==t.which||(t.preventDefault(),o.call(s,t))},!1),s};e.default=c},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createElement=e.toggleClass=e.toggleVisibility=e.show=e.hide=e.removeClass=e.addClass=e.classListContains=e.removeChild=e.querySelectorAll=e.nodeListToArray=e.querySelector=e.appendChild=e.toggleAttribute=e.attributeEquals=e.hasAttribute=e.removeAttribute=e.setAttribute=e.getAttribute=void 0;var o=n(1),i=e.getAttribute=(0,o.curry)(function(t,e){return e.getAttribute(t)}),r=e.setAttribute=(0,o.curry)(function(t,e,n){return n.setAttribute(t,e)}),s=(e.removeAttribute=(0,o.curry)(function(t,e){return e.removeAttribute(t)}),e.hasAttribute=(0,o.curry)(function(t,e){return e.hasAttribute(t)}),e.attributeEquals=(0,o.curry)(function(t,e,n){return n.getAttribute(t)===e}),e.toggleAttribute=(0,o.curry)(function(t,e){var n=i(t,e);r(t,(0,o.inverseBooleanString)(n),e)}),e.appendChild=(0,o.curry)(function(t,e){return t.appendChild(e)}),e.querySelector=(0,o.curry)(function(t,e){return e.querySelector(t)}),e.nodeListToArray=function(t){return Array.prototype.slice.call(t)}),a=(e.querySelectorAll=(0,o.curry)(function(t,e){return s(e.querySelectorAll(t))}),e.removeChild=(0,o.curry)(function(t,e){return t.removeChild(e)}),e.classListContains=(0,o.curry)(function(t,e){return e.classList.contains(t)}),e.addClass=(0,o.curry)(function(t,e){return e.classList.add(t)})),l=e.removeClass=(0,o.curry)(function(t,e){return e.classList.remove(t)}),c=e.hide=a("hidden"),u=e.show=l("hidden");e.toggleVisibility=(0,o.curry)(function(t,e){return(t?u:c)(e)}),e.toggleClass=(0,o.curry)(function(t,e,n){n.classList[e?"add":"remove"](t)}),e.createElement=function(t){var e=t.tag,n=t.id,o=t.classes,i=t.attributes,r=document.createElement(e);return n&&(r.id=n),o&&o.forEach(function(t){r.classList.add(t)}),i&&Object.keys(i).forEach(function(t){r.setAttribute(t,i[t])}),r}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});e.Eventful=function(){return{listeners:{},on:function(t,e,n){var o={listener:e,scope:n};return this.listeners[t]=this.listeners[t]||[],this.listeners[t].push(o),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){var n=this;t.forEach(function(t){return e.on(t,function(e){return n.fire(t,e)})})}}}},function(t,e,n){"use strict";function o(t,e,n){var o=this;H5P.EventDispatcher.call(o);var h,f,v,m,b=t.action;n&&(b.userDatas={state:n});var g,y,k,S,$=b.library.split(" ")[0],w=e.l10n[d($,t)?"content":"interaction"],C="H5P.Nil"!==$&&"button"===t.displayType,T=[b.params.contentName,C?a(t.label):"",t.libraryTitle].filter(s)[0],x=!1,P=!1,E=!1,I=t.action.metadata;this.on("open-dialog",function(){L()}),this.on("show-mask",function(){F(this.getElement())});var A=function(){return r.extend({},{backgroundColor:"rgb(255,255,255)",boxShadow:!0},t.visuals)},B=function(n){h=r("
",{tabindex:0,role:"button",class:"h5p-interaction "+g+(n?"":" h5p-hidden"),"aria-popup":"true","aria-expanded":"false","aria-label":T,css:{left:t.x+"%",top:t.y+"%",width:"",height:""},on:{click:function(){o.dialogDisabled||(L(),h.attr("aria-expanded","true"))},keydown:function(t){13!==t.which&&32!==t.which||o.dialogDisabled||(L(),h.attr("aria-expanded","true"),t.preventDefault())}}}),o.getRequiresCompletion()&&void 0===e.editor&&e.currentState!==H5P.InteractiveVideo.SEEKING&&L(!0),r("
",{class:"h5p-touch-area"}).appendTo(h),r("
",{class:"h5p-interaction-button"}).appendTo(h),e.editor&&h.hover(function(){h.is(".focused")||h.is(":focus")||e.dnb&&(!e.dnb||e.dnb.newElement)?(e.editor.hideInteractionTitle(),x=!1):(e.editor.showInteractionTitle(T,h),x=!0)},function(){e.editor.hideInteractionTitle(),x=!1}).focus(function(){e.editor.hideInteractionTitle(),x=!1}).click(function(){e.editor.hideInteractionTitle()});var i=s(a(t.label));t.label&&i&&(f=M(t.label,"h5p-interaction").appendTo(h)),o.trigger("display",h),setTimeout(function(){h&&h.removeClass("h5p-hidden")},0)},M=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return r("
",{class:"h5p-interaction-label "+e,html:''+t+"
"})},H=function(){return h=M(t.label,"h5p-interaction h5p-interaction-label-standalone"),h.css({left:t.x+"%",top:t.y+"%",width:"",height:"initial"}),o.trigger("display",h),setTimeout(function(){h&&h.removeClass("h5p-hidden")},0),h},D=function(e){if("timecode"===t.goto.type)e.click(function(e){1===e.which&&W({data:t.goto.time})}).keypress(function(e){32===e.which&&W({data:t.goto.time})}).attr("href","#").attr("tabindex","0");else{var n=t.goto.url;e.keypress(function(t){32===t.which&&this.click()}).attr({href:("other"!==n.protocol?n.protocol:"")+n.url,target:"_blank"})}return e.addClass("goto-clickable")},R=function(t){var n=!e.hasUncompletedRequiredInteractions(t);y&&y.trigger("hide"),h&&o.trigger("hide",h),o.isButton()?n&&e.dnb.dialog.close():(e.isMobileView&&n&&e.dnb.dialog.close(),h&&h.detach()),o.trigger("remove",h),n&&K(h)},O=function(){var t=document.createElement("button");return t.innerHTML=e.l10n.continueWithVideo,t.className="h5p-interaction-continue-button",t.addEventListener("click",function(){R(),e.play()}),t},V=function(t){if("H5P.Questionnaire"===$){if(t.find(".h5p-interaction-continue-button").length)return;var n=O(),o=t.find(".h5p-questionnaire-success-center");o.length&&o.get(0).appendChild(n),y.on("noSuccessScreen",function(){R(),e.play()})}"H5P.FreeTextQuestion"===$&&y.on("continue",function(){R(),e.play()})},q=function(t,e){var n=function(t){return e.target===t.get(0)},o=9===e.which,i=t.first(),r=t.last();o&&e.shiftKey&&n(i)?(r.focus(),e.preventDefault()):o&&n(r)&&(i.focus(),e.preventDefault())},L=function(n){var i=e.$container.find(".h5p-dialog-wrapper");"function"==typeof y.setActivityStarted&&"function"==typeof y.getScore&&y.setActivityStarted();var s=o.isGotoClickable(),a=i.find("[tabindex]"),u=r(s?"":"",{class:"h5p-dialog-interaction h5p-frame"});!s&&p($)&&u.attr("tabindex","0"),void 0!==e.editor?u.attr("tabindex",-1):0===a.length&&u.attr("tabindex",0),o.getRequiresCompletion()&&i.keydown(function(t){var e=i.find('[tabindex="0"], button, input').filter(":visible");q(e,t)});var d=s?D(u):u;if(y.attach(d),V(d),X(y)&&(o.score=y.getScore(),o.maxScore=y.getMaxScore()),o.hasFullScore()&&n){if(i.hasClass("h5p-hidden"))return}else o.getRequiresCompletion()&&!o.hasFullScore()&&(e.dnb.dialog.hideCloseButton(),e.dnb.dialog.disableOverlay=!0,i.click(function(){if(!o.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($),e.dnb.dialog.toggleClass("goto-clickable-visualize",!(!s||!t.goto.visualize)),e.dnb.dialog.toggleClass("h5p-goto-timecode",c(t,l.TIME_CODE)),e.dnb.dialog.disableOverlay&&e.restorePosterTabIndexes();var f=function(){e.dnb.$dialogContainer.one("transitionend",function(){if(u.is(".h5p-image")){u.find("img").css({width:"",height:""})}});try{void 0!==y.pause&&(y.pause instanceof Function||"function"==typeof y.pause)&&y.pause()}catch(t){H5P.error(t)}h&&(h.focus(),h.attr("aria-expanded","false")),y&&y.trigger("hide")};e.dnb.dialog.once("close",f);var v=function t(){o.dialogWidth=e.dnb.dialog.getDialogWidth(),e.dnb.dialog.off("close",t)};if(e.dnb.dialog.on("close",v),"H5P.Image"===$){var m=e.dnb.dialog.getMaxSize(h),g=u.find("img");b.params.file.width&&b.params.file.height?N(g,m,{width:b.params.file.width,height:b.params.file.height},!e.isMobileView):(g.on("load",function(){g.is(":visible")&&N(g,m,{width:this.width,height:this.height},!e.isMobileView)}),e.dnb.dialog.position(h))}else e.isMobileView||("H5P.FreeTextQuestion"===$?e.dnb.dialog.position(h,{width:o.dialogWidth/16},"big"):"H5P.Text"!==$&&"H5P.Table"!==$?e.dnb.dialog.position(h,{width:o.dialogWidth/16},"medium"):e.dnb.dialog.position(h,{width:o.dialogWidth/16},null));if("H5P.Summary"===$){var k=0;H5P.on(y,"resize",function(){var t=u.height();(k>t+10||k
n.height&&(o.width=o.width*n.height/o.height,o.height=n.height),o.width>n.width&&(o.height=o.height*n.width/o.width,o.width=n.width);var r=16/Number(t.css("fontSize").replace("px",""));t.css({width:o.width*r+"em",height:o.height*r+"em"}),i&&e.dnb.dialog.position(h,o)},W=function(n){o.isButton()&&e.dnb.dialog.close(),e.currentState!==H5P.Video.PAUSED&&e.currentState!==H5P.Video.ENDED||e.play();var i=n.data;i===t.duration.from&&(i+=.2),e.seek(i)},z=function(){var n=t.height||10,o=t.width||10,i=e.width/e.fontSize;return{height:n/(i/(e.$videoWrapper.width()/e.$videoWrapper.height()))*100+"%",width:o/i*100+"%"}},F=function(t){t.css("zIndex",52),e.showOverlayMask()},K=function(t){t&&t.css("zIndex",""),e.hideOverlayMask()},_=function(){var n=o.isGotoClickable(),i=z(),s=A();if(h=r("
",{"aria-label":e.l10n.interaction,tabindex:"-1",class:"h5p-interaction h5p-poster "+g+(n&&t.goto.visualize?" goto-clickable-visualize":""),css:{left:t.x+"%",top:t.y+"%",width:i.width,height:i.height}}),"H5P.IVHotspot"!==$){h.css("background",s.backgroundColor);var a=s.backgroundColor.split(",");if(a[3]){0===parseFloat(a[3].replace(")",""))&&h.addClass("h5p-transparent-interaction")}}!1===s.boxShadow&&h.addClass("h5p-box-shadow-disabled"),"H5P.Link"===$&&(h.css("height","auto"),h.css("width","auto"),void 0===e.editor&&h.click(function(){return window.open(y.getUrl()),e.pause(),!1})),m=r("",{class:"h5p-interaction-outer"}).appendTo(h),v=r(n?"
":"",{class:"h5p-interaction-inner h5p-frame"}).appendTo(m),!n&&p($)&&v.attr("tabindex","0"),void 0!==e.editor&&y.disableAutoPlay&&y.disableAutoPlay();var l=n?D(v):v;y.attach(l),V(l),o.trigger("display",h),o.getRequiresCompletion()&&e.currentState!==H5P.InteractiveVideo.SEEKING&&void 0===e.editor&&!o.hasFullScore()&&(F(h),h.focus()),setTimeout(function(){H5P.trigger(y,"resize")},0),"function"==typeof y.setActivityStarted&&"function"==typeof y.getScore&&y.setActivityStarted()},j=function(){var n,i,r=!0;if(t.adaptivity&&(i=o.hasFullScore(),r=!o.getRequiresCompletion()||i,i?n=t.adaptivity.correct:i||(n=t.adaptivity.wrong)),!n||void 0===n.seekTo)return void(void 0!==y.hasButton&&(y.hasButton("iv-continue")||y.addButton("iv-continue",e.l10n.defaultAdaptivitySeekLabel,function(){R(),U()}),y[r?"showButton":"hideButton"]("iv-continue")));e.pause(),!n.allowOptOut&&h&&(o.isButton()?(e.dnb.dialog.disableOverlay=!0,e.dnb.dialog.hideCloseButton()):F(h));var s=i?"correct":"wrong",a=n.seekLabel?n.seekLabel:e.l10n.defaultAdaptivitySeekLabel;y.hideButton("iv-continue").addButton("iv-adaptivity-"+s,a,function(){R(n.seekTo),!i&&y.resetTask&&(y.resetTask(),y.hideButton("iv-adaptivity-"+s)),o.remove(),U(n.seekTo)}).showButton("iv-adaptivity-"+s,1).hideButton("iv-adaptivity-"+(i?"wrong":"correct"),1).hideButton("check-answer",1).hideButton("show-solution",1).hideButton("try-again",1),void 0!==y.disableInput&&(y.disableInput instanceof Function||"function"==typeof y.disableInput)&&y.disableInput(),setTimeout(function(){var t=n.message.replace("
","").replace("
","");y.updateFeedbackContent(t,!0),y.read(t)},0)},U=function(t){var n=G(),o=n.filter(function(t){return!t.isButton()});if(o.length?n=o:n.length&&e.$container.find(".h5p-dialog-wrapper .h5p-dialog").show(),n.length){var i=n[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)),e.play(),e.controls.$play.focus())},G=function(){return e.getVisibleInteractions().filter(function(t){return t!==o}).filter(function(t){return t.getRequiresCompletion()&&!t.hasFullScore()})},Q=function(){var e=t.className;if(void 0===e){var n=b.library.split(" ")[0].toLowerCase().split(".");e=n[0]+"-"+n[1]+"-interaction"}return t.goto&&"timecode"===t.goto.type&&(e+=" h5p-goto-timecode"),e};o.isGotoClickable=function(){return-1!==["H5P.Text","H5P.Image"].indexOf($)&&t.goto&&-1!==["timecode","url"].indexOf(t.goto.type)},o.getCurrentState=function(){if(y&&(y.getCurrentState instanceof Function||"function"==typeof y.getCurrentState))return y.getCurrentState()},o.getDuration=function(){return{from:t.duration.from,to:t.duration.to+1}},o.getRequiresCompletion=function(){return!!t.adaptivity&&!!t.adaptivity.requireCompletion},o.pause=function(){return t.pause},o.isButton=function(){return"button"===t.displayType||!(!e.isMobileView||"H5P.IVHotspot"===$)&&("H5P.Image"!==$||!1!==t.buttonOnMobile)},o.isStandaloneLabel=function(){return"H5P.Nil"===$},o.isMainSummary=function(){return!0===t.mainSummary},o.selectDot=function(){e.preventSkipping||(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())))},o.addDot=function(){if("H5P.Nil"===$)return r("
",{class:n});var n="h5p-seekbar-interaction "+g,i=r("
",{role:"menuitem",class:n,"aria-label":w+". "+T,title:T,css:{left:t.duration.from*e.oneSecondInPercentage+"%"},on:{click:o.selectDot,keydown:function(t){if(13===t.which||32===t.which)return o.selectDot(),!1}}});return e.preventSkipping&&i.attr("aria-disabled","true").attr("tabindex","-1"),o.$menuitem=i,i},o.isVisible=function(){return E},o.visibleAt=function(e){return!(e
=t.duration.to+1)},o.toggle=function(n,i){if(!o.visibleAt(n))return E=!1,void(h&&(S&&(S.hideContextMenu(),S===e.dnb.focusedElement&&(S.blur(),delete e.dnb.focusedElement)),e.editor&&x&&(e.editor.hideInteractionTitle(),x=!1),o.remove()));if(!h)return E=!0,o.isStandaloneLabel()?H():o.isButton()?B(i):_(),void 0===e.editor?S=e.dnb.add(h,void 0,{dnbElement:S,disableContextMenu:!0}):(o.fit&&(e.editor.fit(h,t),o.fit=!1),h.focus(function(){e.pause()})),h},o.setTitle=function(t){h&&h.attr("aria-label",t),T=t},o.reCreateInteraction=function(){"H5P.IVHotspot"!==$&&h&&(y&&y.trigger("hide"),o.trigger("hide",h),h.detach(),o.isStandaloneLabel()?H():o.isButton()?B(!0):_())},o.resizeInteraction=function(){o.isStandaloneLabel()||H5P.trigger(y,"resize")},o.positionLabel=function(t){h&&o.isButton()&&f&&!o.isStandaloneLabel()&&(f.removeClass("h5p-left-label"),parseInt(h.css("left"))+f.position().left+f.outerWidth()>t&&f.addClass("h5p-left-label"))},o.setPosition=function(e,n){t.x=e,t.y=n,h.css({left:e+"%",top:n+"%"})},o.setSize=function(e,n){e&&(t.width=e),n&&(t.height=n),H5P.trigger(y,"resize")},o.remove=function(){h&&(o.trigger("domHidden",{$dom:h,key:"videoProgressedPast"},{bubbles:!0,external:!0}),y&&y.trigger("hide"),o.trigger("hide",h),h.detach(),h=void 0)},o.reCreate=function(){g=Q(),o.isStandaloneLabel()||(b.params=b.params||{},y=H5P.newRunnable(b,e.contentId,void 0,void 0,{parent:e,editing:void 0!==e.editor}),void 0===o.maxScore&&y.getMaxScore&&(o.maxScore=y.getMaxScore()),b.userDatas&&X(y)&&(o.score=y.getScore()),e.isTask||void 0===e.options.assets.endscreens||(y.isTask||void 0===y.isTask&&void 0!==y.showSolutions)&&(e.isTask=!0),y.on&&(y.on("xAPI",function(t){var n=t.getVerifiedStatementValue(["context","contextActivities","parent"])||[],i=t.getContentXAPIId(e),r="completed"===t.getVerb()||"answered"===t.getVerb(),s=n.some(function(t){return t.id===i});y.getScore&&(o.score=y.getScore()),y.getMaxScore&&(o.maxScore=y.getMaxScore()),s&&r&&t.getMaxScore()&&(o.score=null==t.getScore()?0:t.getScore(),o.maxScore=o.maxScore?o.maxScore:t.getMaxScore(),j()),o.setLastXAPIVerb(t.getVerb()),o.trigger(t)}),y.on("question-finished",function(){j()}),y.on("resize",function(){delete o.dialogWidth,e&&e.dnb&&e.dnb.dialog.removeStaticWidth()}),"H5P.IVHotspot"===$&&y.on("goto",W),"H5P.GoToQuestion"===$&&y.on("chosen",W)))};var X=function(t){return"undefined"!==(void 0===t?"undefined":i(t))&&"function"==typeof t.getScore&&"function"==typeof t.getMaxScore};o.setDnbElement=function(t){return S!==t&&(S=t,!0)},o.hasFullScore=function(){return o.score>=o.maxScore},o.getLibraryName=function(){return $},o.getMetadata=function(){return I},o.getTitle=function(){return T},o.isAnswerable=function(){return-1===u.indexOf(o.getLibraryName())&&!o.isStandaloneLabel()},o.setProgress=function(t){this.progress=t},o.getProgress=function(){return this.progress},o.setLastXAPIVerb=function(t){k=t},o.getLastXAPIVerb=function(){return k},o.getClass=function(){return g},o.getCopyrights=function(){if(!o.isStandaloneLabel()){var n=H5P.newRunnable(b,e.contentId);if(void 0!==n){var i=new H5P.ContentCopyrights;return i.addContent(H5P.getCopyrights(n,t,e.contentId)),i.setLabel(T+" "+H5P.InteractiveVideo.humanizeTime(t.duration.from)+" - "+H5P.InteractiveVideo.humanizeTime(t.duration.to)),i}}},o.getXAPIData=function(){if(y&&(y.getXAPIData instanceof Function||"function"==typeof y.getXAPIData))return y.getXAPIData()},o.getSubcontentId=function(){return b.subContentId},o.getElement=function(){return h},o.focusOnFirstTabbableElement=function(){if(h){var t=r(h.get(0)).find("[tabindex]");t&&t.length?t.get(0).focus():o.focus()}},o.focus=function(){h&&h.focus()},o.getClipboardData=function(){return H5P.DragNBar.clipboardify(H5PEditor.InteractiveVideo.clipboardKey,t,"action")},o.repositionToWrapper=function(e){if(h&&"H5P.IVHotspot"!==$&&"H5P.FreeTextQuestion"!==$){if(P&&(h.css({top:t.y+"%",left:t.x+"%"}),h.css(o.isButton()?{height:"",width:""}:z()),P=!1),h.position().top+h.height()>e.height()){var n=(e.height()-h.height())/e.height()*100;if(n<0){n=0;var i=e.height()/parseFloat(h.css("font-size"));h.css("height",i+"em")}h.css("top",n+"%"),P=!0}if(h.position().left+h.width()>e.width()){var r=(e.width()-h.width())/e.width()*100;if(r<0){r=0;var s=e.width()/parseFloat(h.css("font-size"));h.css("width",s+"em")}h.css("left",r+"%"),P=!0}}},o.resetTask=function(){void 0!==b.userDatas&&void 0!==b.userDatas.state&&delete b.userDatas.state,delete o.score,delete o.maxScore,o.reCreate()},o.getInstance=function(){return y},o.reCreate()}Object.defineProperty(e,"__esModule",{value:!0});var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r=H5P.jQuery,s=function(t){return void 0!==t&&"string"==typeof t&&t.length>0},a=function(t){return s(t)?r(""+t+"
").text():void 0},l={TIME_CODE:"timecode",URL:"url"},c=function(t,e){return void 0!==t.goto&&t.goto.type===e},u=["H5P.Image","H5P.Nil","H5P.Table","H5P.Link","H5P.GoToQuestion","H5P.IVHotspot","H5P.Text"],d=function(t,e){return-1!==u.indexOf(t)||c(e,l.TIME_CODE)},h=["H5P.Text","H5P.Table"],p=function(t){return-1!==h.indexOf(t)};o.prototype=Object.create(H5P.EventDispatcher.prototype),o.prototype.constructor=o,o.PROGRESS_INTERACTED=0,o.PROGRESS_ANSWERED=1,e.default=o},function(t,e,n){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=function(){function t(t,e){for(var n=0;n0&&(this.interactionsAnnouncer.textContent="",this.interactionsAnnouncer.textContent="\n "+this.getAnnouncementMessage(t.length)+"\n "+this.getTitleAnnouncement(t.length,t[0])+"\n "+this.getPauseAnnouncement(t))}},{key:"getAnnouncementMessage",value:function(t){return 0===t?"":1===t?this.l10n.singleInteractionAnnouncement:this.l10n.multipleInteractionsAnnouncement}},{key:"getTitleAnnouncement",value:function(t,e){return 1===t?e.getTitle():""}},{key:"getPauseAnnouncement",value:function(t){return t.some(function(t){return t.pause()})?". "+this.l10n.videoPausedAnnouncement:""}}]),t}();e.default=r},function(t,e,n){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{content:"",maxWidth:"auto",style:"h5p-interactive-video-bubble",mode:"centered",focus:function(){}};o(this,t),this.$reference=e,this.maxWidth=n.maxWidth,this.style=n.style,this.mode=n.mode,this.focus=n.focus,this.$tail=r("
",{class:this.style+"-tail"}),this.$innerTail=r("
",{class:this.style+"-inner-tail"}),this.$content=r("
",{class:this.style+"-text"}),"string"==typeof n.content?this.$content.html(n.content):this.$content.append(n.content),this.$innerBubble=r("
",{class:this.style+"-inner"}).append(this.$content).prepend(this.$innerTail),this.$h5pContainer=this.$reference.closest(".h5p-interactive-video"),this.$bubble=r("
",{class:this.style,"aria-live":"polite"}).append([this.$tail,this.$innerBubble]).addClass(this.style+"-inactive").appendTo(this.$h5pContainer),s&&H5P.$body.css("cursor","pointer"),"centered"===this.mode&&this.$bubble.css({width:"auto"===this.maxWidth?"auto":this.maxWidth+"px"}),this.update()}return i(t,[{key:"update",value:function(){var t=this,e=this.getOffsetBetween(this.$h5pContainer,this.$reference),n="full"===this.mode?this.$bubble.outerWidth():Math.min(.9*e.outerWidth,"auto"===this.maxWidth?this.$bubble.outerWidth():this.maxWidth),o=this.getBubblePosition(n,e,this.mode);"centered"===this.mode&&this.$bubble.css({bottom:void 0===o.bottom?void 0:o.bottom+"px",left:o.left+"px"}),setTimeout(function(){var e=t.getTailPosition(t.$reference),n={bottom:e.bottom+"px",left:"string"==typeof e.left?e.left:e.left+"px"};t.$tail.css(n),t.$innerTail.css(n)},75)}},{key:"animate",value:function(){var t=this;this.$bubble.hasClass(this.style+"-inactive")&&(this.$bubble.removeClass(this.style+"-inactive").addClass(this.style+"-active"),setTimeout(function(){t.$bubble.removeClass(t.style+"-active").addClass(t.style+"-inactive")},2e3))}},{key:"setContent",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";this.$content.html(t),this.update()}},{key:"getContent",value:function(){return this.$content.get(0).outerHTML}},{key:"isActive",value:function(){return this.$bubble.hasClass(this.style+"-active")}},{key:"toggle",value:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];t=void 0===t?!this.isActive():t,t&&n?(setTimeout(function(){e.$bubble.removeClass(e.style+"-preparing").addClass(e.style+"-active"),setTimeout(function(){return e.focus()},400)},100),this.$bubble.removeClass(this.style+"-inactive").addClass(this.style+"-preparing")):this.$bubble.toggleClass(this.style+"-inactive",!t).toggleClass(this.style+"-active",t),this.update()}},{key:"getBubblePosition",value:function(t,e,n){var o=t/2;return{bottom:"full"===n?void 0:e.bottom+e.innerHeight+4,left:"full"===n?(e.outerWidth-t)/2:e.left-o+16}}},{key:"getTailPosition",value:function(t){return{left:t.offset().left-this.$tail.parent().offset().left+8,top:-6,bottom:-6}}},{key:"getOffsetBetween",value:function(t,e){var n=t[0].getBoundingClientRect(),o=e[0].getBoundingClientRect();return{top:o.top-n.top,right:n.right-o.right,bottom:n.bottom-o.bottom,left:o.left-n.left+parseInt(e.css("marginLeft")),innerWidth:o.width,innerHeight:o.height,outerWidth:n.width,outerHeight:n.height}}},{key:"fullscreen",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,o=this.isMobilePhone(),i=t&&!o&&void 0!==e&&void 0!==n,r={maxHeight:"",top:""};i&&(r.maxHeight="calc("+n+"px - 1em - 9px)",r.top="calc((("+(e-n)+"px + 1em) / 2) - 9px)"),this.$bubble.toggleClass("mobile-fullscreen",o&&t),this.$bubble.css(r)}},{key:"isMobilePhone",value: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}();e.default=a},function(t,e,n){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function r(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var s=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};o(this,e);var r=i(this,(e.__proto__||Object.getPrototypeOf(e)).call(this));return r.parent=t,r.l10n=l.extend({title:"@answered Questions answered",information:"You have answered @answered questions, click below to submit your answers.",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),r.buildDOM(),r}return r(e,t),s(e,[{key:"buildDOM",value:function(){var t=this;this.$endscreenIntroductionTitleText=l("
",{class:c+"-introduction-title-text",id:c+"-introduction-title-text"}),this.$closeButton=l("",{role:"button",class:c+"-close-button",tabindex:"0","aria-label":this.parent.l10n.close}),(0,a.onClick)(this.$closeButton,function(){return t.parent.toggleEndscreen(!1)});var e=l("
",{class:c+"-introduction-title"}).append([this.$endscreenIntroductionTitleText,this.$closeButton]);this.$endscreenIntroductionText=l("
",{class:c+"-introduction-text",id:c+"-introduction-text"}),this.$submitButtonContainer=l("
",{class:c+"-submit-button-container "+u}),this.$submitButton=H5P.JoubelUI.createButton({class:c+"-submit-button",html:this.l10n.submitButton,appendTo:this.$submitButtonContainer,click:function(){return t.handleSubmit()}}),this.$endscreenOverviewTitle=l("
",{class:c+"-overview-title"}).append(l("
",{class:c+"-overview-title-answered-questions",html:this.l10n.tableRowAnswered})).append(l("
",{class:c+"-overview-title-score",html:this.l10n.tableRowScore})),this.$endscreenBottomTable=l("
",{class:c+"-overview-table"}),this.$endscreen=l("
",{class:c,role:"dialog","aria-labelledby":c+"-introduction-title-text","aria-describedby":c+"-introduction-text"}).append(l("
",{class:c+"-introduction"}).append(l("
",{class:c+"-star-symbol"})).append(l("
",{class:c+"-introduction-container"}).append([e,this.$endscreenIntroductionText,this.$submitButtonContainer]))).append(l("
",{class:c+"-overview"}).append(this.$endscreenOverviewTitle).append(this.$endscreenBottomTable))}},{key:"handleSubmit",value:function(){var t=this;this.$submitButtonContainer.hasClass(u)||(this.$submitButtonContainer.addClass(u),this.$endscreenIntroductionText.html('
'+this.l10n.submitMessage+"
"),this.answered.forEach(function(e){if("completed"!==e.getLastXAPIVerb()&&"answered"!==e.getLastXAPIVerb()){var n=new H5P.XAPIEvent;n.data.statement=e.getXAPIData().statement,e.setLastXAPIVerb(n.getVerb()),t.trigger(n)}}),this.parent.triggerXAPIScored(this.parent.getUsersScore(),this.parent.getUsersMaxScore(),"completed"))}},{key:"getDOM",value:function(){return this.$endscreen}},{key:"buildTableRow",value:function(t,e,n,o){var i=this,r=d(n)&&d(o),s=r?this.l10n.tableRowSummaryWithScore:this.l10n.tableRowSummaryWithoutScore,u=this.parent.skippingPrevented()?" "+c+"-no-link":"",h=l("
",{class:c+"-overview-table-row"+u,role:"row",tabIndex:0,"aria-label":s.replace("@score",n).replace("@total",o).replace("@question",e).replace("@minutes",Math.floor(t/60)).replace("@seconds",t%60)});return(0,a.onClick)(h,function(){return i.jump(t)}),l("
",{class:c+"-overview-table-row-time",html:H5P.InteractiveVideo.humanizeTime(t),appendTo:h,"aria-hidden":!0}),l("
",{class:c+"-overview-table-row-title",html:e,appendTo:h,"aria-hidden":!0}),l("
",{class:c+"-overview-table-row-score",html:r?n+" / "+o:this.l10n.answeredScore,appendTo:h,"aria-hidden":!0}),h}},{key:"jump",value:function(t){this.parent.skippingPrevented()||(this.parent.seek(t),this.parent.toggleEndscreen(!1))}},{key:"update",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.answered=e.filter(function(t){return void 0!==t.getProgress()}).sort(function(t,e){return t.getDuration().from-e.getDuration().from}),this.$endscreenBottomTable.empty(),this.answered.forEach(function(e){var n=e.getDuration().from,o=t.getDescription(e),i=e.getInstance(),r=i.getScore?i.getScore():void 0,s=i.getMaxScore?i.getMaxScore():void 0;t.$endscreenBottomTable.append(t.buildTableRow(n,o,r,s))});var n=this.answered.length;this.$endscreenIntroductionTitleText.html(this.l10n.title.replace("@answered",n)),0===n?this.$endscreenIntroductionText.html('
'+this.l10n.informationNoAnswers+"
"+this.l10n.informationMustHaveAnswer+"
"):this.$endscreenIntroductionText.html(this.l10n.information.replace("@answered",n)),n>0&&this.$submitButtonContainer.removeClass(u)}},{key:"getDescription",value:function(t){return"function"==typeof t.getInstance&&"function"==typeof t.getInstance().getTitle?t.getInstance().getTitle():t.getTitle()}},{key:"focus",value:function(){this.$submitButtonContainer.hasClass(u)?this.$closeButton.focus():this.$submitButton.focus()}}]),e}(H5P.EventDispatcher);e.default=h},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.onClick=void 0;var o=n(3);e.onClick=function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=n.ignoreKeyboard,r=void 0!==i&&i,s=n.preventDefaultForKeys,a=void 0===s||s,l=function(t){"click"!==t.type&&a&&t.preventDefault(),e(t)};t.click(l),!0!==r&&(0,o.onKey)(t,[{key:o.Keys.SPACE},{key:o.Keys.ENTER}],l)}}]);;