From d283f7e661e14d6ae1881fe803e5b4f1ed0689ff Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 24 Jun 2024 13:49:08 -0400 Subject: Add 2024 Guix social talk. --- 2024-06-18-guix-social/reveal.js/js/utils/color.js | 77 +++++ .../reveal.js/js/utils/constants.js | 17 ++ .../reveal.js/js/utils/device.js | 8 + .../reveal.js/js/utils/loader.js | 46 +++ 2024-06-18-guix-social/reveal.js/js/utils/util.js | 313 +++++++++++++++++++++ 5 files changed, 461 insertions(+) create mode 100644 2024-06-18-guix-social/reveal.js/js/utils/color.js create mode 100644 2024-06-18-guix-social/reveal.js/js/utils/constants.js create mode 100644 2024-06-18-guix-social/reveal.js/js/utils/device.js create mode 100644 2024-06-18-guix-social/reveal.js/js/utils/loader.js create mode 100644 2024-06-18-guix-social/reveal.js/js/utils/util.js (limited to '2024-06-18-guix-social/reveal.js/js/utils') diff --git a/2024-06-18-guix-social/reveal.js/js/utils/color.js b/2024-06-18-guix-social/reveal.js/js/utils/color.js new file mode 100644 index 0000000..e28a2b1 --- /dev/null +++ b/2024-06-18-guix-social/reveal.js/js/utils/color.js @@ -0,0 +1,77 @@ +/** + * Converts various color input formats to an {r:0,g:0,b:0} object. + * + * @param {string} color The string representation of a color + * @example + * colorToRgb('#000'); + * @example + * colorToRgb('#000000'); + * @example + * colorToRgb('rgb(0,0,0)'); + * @example + * colorToRgb('rgba(0,0,0)'); + * + * @return {{r: number, g: number, b: number, [a]: number}|null} + */ +export const colorToRgb = ( color ) => { + + let hex3 = color.match( /^#([0-9a-f]{3})$/i ); + if( hex3 && hex3[1] ) { + hex3 = hex3[1]; + return { + r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11, + g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11, + b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11 + }; + } + + let hex6 = color.match( /^#([0-9a-f]{6})$/i ); + if( hex6 && hex6[1] ) { + hex6 = hex6[1]; + return { + r: parseInt( hex6.slice( 0, 2 ), 16 ), + g: parseInt( hex6.slice( 2, 4 ), 16 ), + b: parseInt( hex6.slice( 4, 6 ), 16 ) + }; + } + + let rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i ); + if( rgb ) { + return { + r: parseInt( rgb[1], 10 ), + g: parseInt( rgb[2], 10 ), + b: parseInt( rgb[3], 10 ) + }; + } + + let rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i ); + if( rgba ) { + return { + r: parseInt( rgba[1], 10 ), + g: parseInt( rgba[2], 10 ), + b: parseInt( rgba[3], 10 ), + a: parseFloat( rgba[4] ) + }; + } + + return null; + +} + +/** + * Calculates brightness on a scale of 0-255. + * + * @param {string} color See colorToRgb for supported formats. + * @see {@link colorToRgb} + */ +export const colorBrightness = ( color ) => { + + if( typeof color === 'string' ) color = colorToRgb( color ); + + if( color ) { + return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000; + } + + return null; + +} \ No newline at end of file diff --git a/2024-06-18-guix-social/reveal.js/js/utils/constants.js b/2024-06-18-guix-social/reveal.js/js/utils/constants.js new file mode 100644 index 0000000..91a9f22 --- /dev/null +++ b/2024-06-18-guix-social/reveal.js/js/utils/constants.js @@ -0,0 +1,17 @@ + +export const SLIDES_SELECTOR = '.slides section'; +export const HORIZONTAL_SLIDES_SELECTOR = '.slides>section'; +export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section'; +export const HORIZONTAL_BACKGROUNDS_SELECTOR = '.backgrounds>.slide-background'; + +// Methods that may not be invoked via the postMessage API +export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/; + +// Regex for retrieving the fragment style from a class attribute +export const FRAGMENT_STYLE_REGEX = /fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/; + +// Slide number formats +export const SLIDE_NUMBER_FORMAT_HORIZONTAL_DOT_VERTICAL = 'h.v'; +export const SLIDE_NUMBER_FORMAT_HORIZONTAL_SLASH_VERTICAL = 'h/v'; +export const SLIDE_NUMBER_FORMAT_CURRENT = 'c'; +export const SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL = 'c/t'; \ No newline at end of file diff --git a/2024-06-18-guix-social/reveal.js/js/utils/device.js b/2024-06-18-guix-social/reveal.js/js/utils/device.js new file mode 100644 index 0000000..f2bce20 --- /dev/null +++ b/2024-06-18-guix-social/reveal.js/js/utils/device.js @@ -0,0 +1,8 @@ +const UA = navigator.userAgent; + +export const isMobile = /(iphone|ipod|ipad|android)/gi.test( UA ) || + ( navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 ); // iPadOS + +export const isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA ); + +export const isAndroid = /android/gi.test( UA ); \ No newline at end of file diff --git a/2024-06-18-guix-social/reveal.js/js/utils/loader.js b/2024-06-18-guix-social/reveal.js/js/utils/loader.js new file mode 100644 index 0000000..58d39ac --- /dev/null +++ b/2024-06-18-guix-social/reveal.js/js/utils/loader.js @@ -0,0 +1,46 @@ +/** + * Loads a JavaScript file from the given URL and executes it. + * + * @param {string} url Address of the .js file to load + * @param {function} callback Method to invoke when the script + * has loaded and executed + */ +export const loadScript = ( url, callback ) => { + + const script = document.createElement( 'script' ); + script.type = 'text/javascript'; + script.async = false; + script.defer = false; + script.src = url; + + if( typeof callback === 'function' ) { + + // Success callback + script.onload = script.onreadystatechange = event => { + if( event.type === 'load' || /loaded|complete/.test( script.readyState ) ) { + + // Kill event listeners + script.onload = script.onreadystatechange = script.onerror = null; + + callback(); + + } + }; + + // Error callback + script.onerror = err => { + + // Kill event listeners + script.onload = script.onreadystatechange = script.onerror = null; + + callback( new Error( 'Failed loading script: ' + script.src + '\n' + err ) ); + + }; + + } + + // Append the script at the end of + const head = document.querySelector( 'head' ); + head.insertBefore( script, head.lastChild ); + +} \ No newline at end of file diff --git a/2024-06-18-guix-social/reveal.js/js/utils/util.js b/2024-06-18-guix-social/reveal.js/js/utils/util.js new file mode 100644 index 0000000..a5515e8 --- /dev/null +++ b/2024-06-18-guix-social/reveal.js/js/utils/util.js @@ -0,0 +1,313 @@ +/** + * 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()] +} + +/** + * Encodes a string for RFC3986-compliant URL format. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encoding_for_rfc3986 + * + * @param {string} url + */ +export const encodeRFC3986URI = ( url='' ) => { + return encodeURI(url) + .replace(/%5B/g, "[") + .replace(/%5D/g, "]") + .replace( + /[!'()*]/g, + (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}` + ); +} \ No newline at end of file -- cgit v1.2.3