| /** |
| * Extend object a with the properties of object b. |
| * If there's a conflict, object b takes precedence. |
| * |
| * @param {object} a |
| * @param {object} b |
| */ |
| export const extend = ( a, b ) => { |
| |
| for( let i in b ) { |
| a[ i ] = b[ i ]; |
| } |
| |
| return a; |
| |
| } |
| |
| /** |
| * querySelectorAll but returns an Array. |
| */ |
| export const queryAll = ( el, selector ) => { |
| |
| return Array.from( el.querySelectorAll( selector ) ); |
| |
| } |
| |
| /** |
| * classList.toggle() with cross browser support |
| */ |
| export const toggleClass = ( el, className, value ) => { |
| if( value ) { |
| el.classList.add( className ); |
| } |
| else { |
| el.classList.remove( className ); |
| } |
| } |
| |
| /** |
| * Utility for deserializing a value. |
| * |
| * @param {*} value |
| * @return {*} |
| */ |
| export const deserialize = ( value ) => { |
| |
| if( typeof value === 'string' ) { |
| if( value === 'null' ) return null; |
| else if( value === 'true' ) return true; |
| else if( value === 'false' ) return false; |
| else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value ); |
| } |
| |
| return value; |
| |
| } |
| |
| /** |
| * Measures the distance in pixels between point a |
| * and point b. |
| * |
| * @param {object} a point with x/y properties |
| * @param {object} b point with x/y properties |
| * |
| * @return {number} |
| */ |
| export const distanceBetween = ( a, b ) => { |
| |
| let dx = a.x - b.x, |
| dy = a.y - b.y; |
| |
| return Math.sqrt( dx*dx + dy*dy ); |
| |
| } |
| |
| /** |
| * Applies a CSS transform to the target element. |
| * |
| * @param {HTMLElement} element |
| * @param {string} transform |
| */ |
| export const transformElement = ( element, transform ) => { |
| |
| element.style.transform = transform; |
| |
| } |
| |
| /** |
| * Element.matches with IE support. |
| * |
| * @param {HTMLElement} target The element to match |
| * @param {String} selector The CSS selector to match |
| * the element against |
| * |
| * @return {Boolean} |
| */ |
| export const matches = ( target, selector ) => { |
| |
| let matchesMethod = target.matches || target.matchesSelector || target.msMatchesSelector; |
| |
| return !!( matchesMethod && matchesMethod.call( target, selector ) ); |
| |
| } |
| |
| /** |
| * Find the closest parent that matches the given |
| * selector. |
| * |
| * @param {HTMLElement} target The child element |
| * @param {String} selector The CSS selector to match |
| * the parents against |
| * |
| * @return {HTMLElement} The matched parent or null |
| * if no matching parent was found |
| */ |
| export const closest = ( target, selector ) => { |
| |
| // Native Element.closest |
| if( typeof target.closest === 'function' ) { |
| return target.closest( selector ); |
| } |
| |
| // Polyfill |
| while( target ) { |
| if( matches( target, selector ) ) { |
| return target; |
| } |
| |
| // Keep searching |
| target = target.parentNode; |
| } |
| |
| return null; |
| |
| } |
| |
| /** |
| * Handling the fullscreen functionality via the fullscreen API |
| * |
| * @see http://fullscreen.spec.whatwg.org/ |
| * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode |
| */ |
| export const enterFullscreen = element => { |
| |
| element = element || document.documentElement; |
| |
| // Check which implementation is available |
| let requestMethod = element.requestFullscreen || |
| element.webkitRequestFullscreen || |
| element.webkitRequestFullScreen || |
| element.mozRequestFullScreen || |
| element.msRequestFullscreen; |
| |
| if( requestMethod ) { |
| requestMethod.apply( element ); |
| } |
| |
| } |
| |
| /** |
| * Creates an HTML element and returns a reference to it. |
| * If the element already exists the existing instance will |
| * be returned. |
| * |
| * @param {HTMLElement} container |
| * @param {string} tagname |
| * @param {string} classname |
| * @param {string} innerHTML |
| * |
| * @return {HTMLElement} |
| */ |
| export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => { |
| |
| // Find all nodes matching the description |
| let nodes = container.querySelectorAll( '.' + classname ); |
| |
| // Check all matches to find one which is a direct child of |
| // the specified container |
| for( let i = 0; i < nodes.length; i++ ) { |
| let testNode = nodes[i]; |
| if( testNode.parentNode === container ) { |
| return testNode; |
| } |
| } |
| |
| // If no node was found, create it now |
| let node = document.createElement( tagname ); |
| node.className = classname; |
| node.innerHTML = innerHTML; |
| container.appendChild( node ); |
| |
| return node; |
| |
| } |
| |
| /** |
| * Injects the given CSS styles into the DOM. |
| * |
| * @param {string} value |
| */ |
| export const createStyleSheet = ( value ) => { |
| |
| let tag = document.createElement( 'style' ); |
| tag.type = 'text/css'; |
| |
| if( value && value.length > 0 ) { |
| if( tag.styleSheet ) { |
| tag.styleSheet.cssText = value; |
| } |
| else { |
| tag.appendChild( document.createTextNode( value ) ); |
| } |
| } |
| |
| document.head.appendChild( tag ); |
| |
| return tag; |
| |
| } |
| |
| /** |
| * Returns a key:value hash of all query params. |
| */ |
| export const getQueryHash = () => { |
| |
| let query = {}; |
| |
| location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, a => { |
| query[ a.split( '=' ).shift() ] = a.split( '=' ).pop(); |
| } ); |
| |
| // Basic deserialization |
| for( let i in query ) { |
| let value = query[ i ]; |
| |
| query[ i ] = deserialize( unescape( value ) ); |
| } |
| |
| // Do not accept new dependencies via query config to avoid |
| // the potential of malicious script injection |
| if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies']; |
| |
| return query; |
| |
| } |
| |
| /** |
| * Returns the remaining height within the parent of the |
| * target element. |
| * |
| * remaining height = [ configured parent height ] - [ current parent height ] |
| * |
| * @param {HTMLElement} element |
| * @param {number} [height] |
| */ |
| export const getRemainingHeight = ( element, height = 0 ) => { |
| |
| if( element ) { |
| let newHeight, oldHeight = element.style.height; |
| |
| // Change the .stretch element height to 0 in order find the height of all |
| // the other elements |
| element.style.height = '0px'; |
| |
| // In Overview mode, the parent (.slide) height is set of 700px. |
| // Restore it temporarily to its natural height. |
| element.parentNode.style.height = 'auto'; |
| |
| newHeight = height - element.parentNode.offsetHeight; |
| |
| // Restore the old height, just in case |
| element.style.height = oldHeight + 'px'; |
| |
| // Clear the parent (.slide) height. .removeProperty works in IE9+ |
| element.parentNode.style.removeProperty('height'); |
| |
| return newHeight; |
| } |
| |
| return height; |
| |
| } |
| |
| const fileExtensionToMimeMap = { |
| 'mp4': 'video/mp4', |
| 'm4a': 'video/mp4', |
| 'ogv': 'video/ogg', |
| 'mpeg': 'video/mpeg', |
| 'webm': 'video/webm' |
| } |
| |
| /** |
| * Guess the MIME type for common file formats. |
| */ |
| export const getMimeTypeFromFile = ( filename='' ) => { |
| return fileExtensionToMimeMap[filename.split('.').pop()] |
| } |