aboutsummaryrefslogtreecommitdiffstats
path: root/src/js/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/utils.js')
-rw-r--r--src/js/utils.js875
1 files changed, 0 insertions, 875 deletions
diff --git a/src/js/utils.js b/src/js/utils.js
deleted file mode 100644
index c36763dd..00000000
--- a/src/js/utils.js
+++ /dev/null
@@ -1,875 +0,0 @@
-// ==========================================================================
-// Plyr utils
-// ==========================================================================
-
-import loadjs from 'loadjs';
-import Storage from './storage';
-import support from './support';
-import { providers } from './types';
-
-const utils = {
- // Check variable types
- is: {
- object(input) {
- return utils.getConstructor(input) === Object;
- },
- number(input) {
- return utils.getConstructor(input) === Number && !Number.isNaN(input);
- },
- string(input) {
- return utils.getConstructor(input) === String;
- },
- boolean(input) {
- return utils.getConstructor(input) === Boolean;
- },
- function(input) {
- return utils.getConstructor(input) === Function;
- },
- array(input) {
- return !utils.is.nullOrUndefined(input) && Array.isArray(input);
- },
- weakMap(input) {
- return utils.is.instanceof(input, WeakMap);
- },
- nodeList(input) {
- return utils.is.instanceof(input, NodeList);
- },
- element(input) {
- return utils.is.instanceof(input, Element);
- },
- textNode(input) {
- return utils.getConstructor(input) === Text;
- },
- event(input) {
- return utils.is.instanceof(input, Event);
- },
- cue(input) {
- return utils.is.instanceof(input, window.TextTrackCue) || utils.is.instanceof(input, window.VTTCue);
- },
- track(input) {
- return utils.is.instanceof(input, TextTrack) || (!utils.is.nullOrUndefined(input) && utils.is.string(input.kind));
- },
- url(input) {
- return !utils.is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
- },
- nullOrUndefined(input) {
- return input === null || typeof input === 'undefined';
- },
- empty(input) {
- return (
- utils.is.nullOrUndefined(input) ||
- ((utils.is.string(input) || utils.is.array(input) || utils.is.nodeList(input)) && !input.length) ||
- (utils.is.object(input) && !Object.keys(input).length)
- );
- },
- instanceof(input, constructor) {
- return Boolean(input && constructor && input instanceof constructor);
- },
- },
-
- getConstructor(input) {
- return !utils.is.nullOrUndefined(input) ? input.constructor : null;
- },
-
- // Unfortunately, due to mixed support, UA sniffing is required
- getBrowser() {
- return {
- isIE: /* @cc_on!@ */ false || !!document.documentMode,
- isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
- isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
- isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform),
- };
- },
-
- // Fetch wrapper
- // Using XHR to avoid issues with older browsers
- fetch(url, responseType = 'text') {
- return new Promise((resolve, reject) => {
- try {
- const request = new XMLHttpRequest();
-
- // Check for CORS support
- if (!('withCredentials' in request)) {
- return;
- }
-
- request.addEventListener('load', () => {
- if (responseType === 'text') {
- try {
- resolve(JSON.parse(request.responseText));
- } catch (e) {
- resolve(request.responseText);
- }
- } else {
- resolve(request.response);
- }
- });
-
- request.addEventListener('error', () => {
- throw new Error(request.statusText);
- });
-
- request.open('GET', url, true);
-
- // Set the required response type
- request.responseType = responseType;
-
- request.send();
- } catch (e) {
- reject(e);
- }
- });
- },
-
- // Load image avoiding xhr/fetch CORS issues
- // Server status can't be obtained this way unfortunately, so this uses "naturalWidth" to determine if the image has loaded.
- // By default it checks if it is at least 1px, but you can add a second argument to change this.
- loadImage(src, minWidth = 1) {
- return new Promise((resolve, reject) => {
- const image = new Image();
- const handler = () => {
- delete image.onload;
- delete image.onerror;
- (image.naturalWidth >= minWidth ? resolve : reject)(image);
- };
- Object.assign(image, {onload: handler, onerror: handler, src});
- });
- },
-
- // Load an external script
- loadScript(url) {
- return new Promise((resolve, reject) => {
- loadjs(url, {
- success: resolve,
- error: reject,
- });
- });
- },
-
- // Load an external SVG sprite
- loadSprite(url, id) {
- if (!utils.is.string(url)) {
- return;
- }
-
- const prefix = 'cache';
- const hasId = utils.is.string(id);
- let isCached = false;
-
- const exists = () => document.getElementById(id) !== null;
-
- const update = (container, data) => {
- container.innerHTML = data;
-
- // Check again incase of race condition
- if (hasId && exists()) {
- return;
- }
-
- // Inject the SVG to the body
- document.body.insertAdjacentElement('afterbegin', container);
- };
-
- // Only load once if ID set
- if (!hasId || !exists()) {
- const useStorage = Storage.supported;
-
- // Create container
- const container = document.createElement('div');
- utils.toggleHidden(container, true);
-
- if (hasId) {
- container.setAttribute('id', id);
- }
-
- // Check in cache
- if (useStorage) {
- const cached = window.localStorage.getItem(`${prefix}-${id}`);
- isCached = cached !== null;
-
- if (isCached) {
- const data = JSON.parse(cached);
- update(container, data.content);
- }
- }
-
- // Get the sprite
- utils
- .fetch(url)
- .then(result => {
- if (utils.is.empty(result)) {
- return;
- }
-
- if (useStorage) {
- window.localStorage.setItem(
- `${prefix}-${id}`,
- JSON.stringify({
- content: result,
- }),
- );
- }
-
- update(container, result);
- })
- .catch(() => {});
- }
- },
-
- // Generate a random ID
- generateId(prefix) {
- return `${prefix}-${Math.floor(Math.random() * 10000)}`;
- },
-
- // Wrap an element
- wrap(elements, wrapper) {
- // Convert `elements` to an array, if necessary.
- const targets = elements.length ? elements : [elements];
-
- // Loops backwards to prevent having to clone the wrapper on the
- // first element (see `child` below).
- Array.from(targets)
- .reverse()
- .forEach((element, index) => {
- const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
-
- // Cache the current parent and sibling.
- const parent = element.parentNode;
- const sibling = element.nextSibling;
-
- // Wrap the element (is automatically removed from its current
- // parent).
- child.appendChild(element);
-
- // If the element had a sibling, insert the wrapper before
- // the sibling to maintain the HTML structure; otherwise, just
- // append it to the parent.
- if (sibling) {
- parent.insertBefore(child, sibling);
- } else {
- parent.appendChild(child);
- }
- });
- },
-
- // Create a DocumentFragment
- createElement(type, attributes, text) {
- // Create a new <element>
- const element = document.createElement(type);
-
- // Set all passed attributes
- if (utils.is.object(attributes)) {
- utils.setAttributes(element, attributes);
- }
-
- // Add text node
- if (utils.is.string(text)) {
- element.innerText = text;
- }
-
- // Return built element
- return element;
- },
-
- // Inaert an element after another
- insertAfter(element, target) {
- target.parentNode.insertBefore(element, target.nextSibling);
- },
-
- // Insert a DocumentFragment
- insertElement(type, parent, attributes, text) {
- // Inject the new <element>
- parent.appendChild(utils.createElement(type, attributes, text));
- },
-
- // Remove element(s)
- removeElement(element) {
- if (utils.is.nodeList(element) || utils.is.array(element)) {
- Array.from(element).forEach(utils.removeElement);
- return;
- }
-
- if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
- return;
- }
-
- element.parentNode.removeChild(element);
- },
-
- // Remove all child elements
- emptyElement(element) {
- let { length } = element.childNodes;
-
- while (length > 0) {
- element.removeChild(element.lastChild);
- length -= 1;
- }
- },
-
- // Replace element
- replaceElement(newChild, oldChild) {
- if (!utils.is.element(oldChild) || !utils.is.element(oldChild.parentNode) || !utils.is.element(newChild)) {
- return null;
- }
-
- oldChild.parentNode.replaceChild(newChild, oldChild);
-
- return newChild;
- },
-
- // Set attributes
- setAttributes(element, attributes) {
- if (!utils.is.element(element) || utils.is.empty(attributes)) {
- return;
- }
-
- Object.entries(attributes).forEach(([
- key,
- value,
- ]) => {
- element.setAttribute(key, value);
- });
- },
-
- // Get an attribute object from a string selector
- getAttributesFromSelector(sel, existingAttributes) {
- // For example:
- // '.test' to { class: 'test' }
- // '#test' to { id: 'test' }
- // '[data-test="test"]' to { 'data-test': 'test' }
-
- if (!utils.is.string(sel) || utils.is.empty(sel)) {
- return {};
- }
-
- const attributes = {};
- const existing = existingAttributes;
-
- sel.split(',').forEach(s => {
- // Remove whitespace
- const selector = s.trim();
- const className = selector.replace('.', '');
- const stripped = selector.replace(/[[\]]/g, '');
-
- // Get the parts and value
- const parts = stripped.split('=');
- const key = parts[0];
- const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
-
- // Get the first character
- const start = selector.charAt(0);
-
- switch (start) {
- case '.':
- // Add to existing classname
- if (utils.is.object(existing) && utils.is.string(existing.class)) {
- existing.class += ` ${className}`;
- }
-
- attributes.class = className;
- break;
-
- case '#':
- // ID selector
- attributes.id = selector.replace('#', '');
- break;
-
- case '[':
- // Attribute selector
- attributes[key] = value;
-
- break;
-
- default:
- break;
- }
- });
-
- return attributes;
- },
-
- // Toggle hidden
- toggleHidden(element, hidden) {
- if (!utils.is.element(element)) {
- return;
- }
-
- let hide = hidden;
-
- if (!utils.is.boolean(hide)) {
- hide = !element.hasAttribute('hidden');
- }
-
- if (hide) {
- element.setAttribute('hidden', '');
- } else {
- element.removeAttribute('hidden');
- }
- },
-
- // Mirror Element.classList.toggle, with IE compatibility for "force" argument
- toggleClass(element, className, force) {
- if (utils.is.element(element)) {
- let method = 'toggle';
- if (typeof force !== 'undefined') {
- method = force ? 'add' : 'remove';
- }
-
- element.classList[method](className);
- return element.classList.contains(className);
- }
-
- return null;
- },
-
- // Has class name
- hasClass(element, className) {
- return utils.is.element(element) && element.classList.contains(className);
- },
-
- // Element matches selector
- matches(element, selector) {
- const prototype = { Element };
-
- function match() {
- return Array.from(document.querySelectorAll(selector)).includes(this);
- }
-
- const matches = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
-
- return matches.call(element, selector);
- },
-
- // Find all elements
- getElements(selector) {
- return this.elements.container.querySelectorAll(selector);
- },
-
- // Find a single element
- getElement(selector) {
- return this.elements.container.querySelector(selector);
- },
-
- // Get the focused element
- getFocusElement() {
- let focused = document.activeElement;
-
- if (!focused || focused === document.body) {
- focused = null;
- } else {
- focused = document.querySelector(':focus');
- }
-
- return focused;
- },
-
- // Trap focus inside container
- trapFocus(element = null, toggle = false) {
- if (!utils.is.element(element)) {
- return;
- }
-
- const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
- const first = focusable[0];
- const last = focusable[focusable.length - 1];
-
- const trap = event => {
- // Bail if not tab key or not fullscreen
- if (event.key !== 'Tab' || event.keyCode !== 9) {
- return;
- }
-
- // Get the current focused element
- const focused = utils.getFocusElement();
-
- if (focused === last && !event.shiftKey) {
- // Move focus to first element that can be tabbed if Shift isn't used
- first.focus();
- event.preventDefault();
- } else if (focused === first && event.shiftKey) {
- // Move focus to last element that can be tabbed if Shift is used
- last.focus();
- event.preventDefault();
- }
- };
-
- if (toggle) {
- utils.on(this.elements.container, 'keydown', trap, false);
- } else {
- utils.off(this.elements.container, 'keydown', trap, false);
- }
- },
-
- // Toggle event listener
- toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) {
- // Bail if no elemetns, event, or callback
- if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
- return;
- }
-
- // If a nodelist is passed, call itself on each node
- if (utils.is.nodeList(elements) || utils.is.array(elements)) {
- // Create listener for each node
- Array.from(elements).forEach(element => {
- if (element instanceof Node) {
- utils.toggleListener.call(null, element, event, callback, toggle, passive, capture);
- }
- });
-
- return;
- }
-
- // Allow multiple events
- const events = event.split(' ');
-
- // Build options
- // Default to just the capture boolean for browsers with no passive listener support
- let options = capture;
-
- // If passive events listeners are supported
- if (support.passiveListeners) {
- options = {
- // Whether the listener can be passive (i.e. default never prevented)
- passive,
- // Whether the listener is a capturing listener or not
- capture,
- };
- }
-
- // If a single node is passed, bind the event listener
- events.forEach(type => {
- elements[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
- });
- },
-
- // Bind event handler
- on(element, events = '', callback, passive = true, capture = false) {
- utils.toggleListener(element, events, callback, true, passive, capture);
- },
-
- // Unbind event handler
- off(element, events = '', callback, passive = true, capture = false) {
- utils.toggleListener(element, events, callback, false, passive, capture);
- },
-
- // Trigger event
- dispatchEvent(element, type = '', bubbles = false, detail = {}) {
- // Bail if no element
- if (!utils.is.element(element) || utils.is.empty(type)) {
- return;
- }
-
- // Create and dispatch the event
- const event = new CustomEvent(type, {
- bubbles,
- detail: Object.assign({}, detail, {
- plyr: this,
- }),
- });
-
- // Dispatch the event
- element.dispatchEvent(event);
- },
-
- // Toggle aria-pressed state on a toggle button
- // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
- toggleState(element, input) {
- // If multiple elements passed
- if (utils.is.array(element) || utils.is.nodeList(element)) {
- Array.from(element).forEach(target => utils.toggleState(target, input));
- return;
- }
-
- // Bail if no target
- if (!utils.is.element(element)) {
- return;
- }
-
- // Get state
- const pressed = element.getAttribute('aria-pressed') === 'true';
- const state = utils.is.boolean(input) ? input : !pressed;
-
- // Set the attribute on target
- element.setAttribute('aria-pressed', state);
- },
-
- // Format string
- format(input, ...args) {
- if (utils.is.empty(input)) {
- return input;
- }
-
- return input.toString().replace(/{(\d+)}/g, (match, i) => (utils.is.string(args[i]) ? args[i] : ''));
- },
-
- // Get percentage
- getPercentage(current, max) {
- if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
- return 0;
- }
-
- return (current / max * 100).toFixed(2);
- },
-
- // Time helpers
- getHours(value) {
- return parseInt((value / 60 / 60) % 60, 10);
- },
- getMinutes(value) {
- return parseInt((value / 60) % 60, 10);
- },
- getSeconds(value) {
- return parseInt(value % 60, 10);
- },
-
- // Format time to UI friendly string
- formatTime(time = 0, displayHours = false, inverted = false) {
- // Bail if the value isn't a number
- if (!utils.is.number(time)) {
- return utils.formatTime(null, displayHours, inverted);
- }
-
- // Format time component to add leading zero
- const format = value => `0${value}`.slice(-2);
-
- // Breakdown to hours, mins, secs
- let hours = utils.getHours(time);
- const mins = utils.getMinutes(time);
- const secs = utils.getSeconds(time);
-
- // Do we need to display hours?
- if (displayHours || hours > 0) {
- hours = `${hours}:`;
- } else {
- hours = '';
- }
-
- // Render
- return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
- },
-
- // Replace all occurances of a string in a string
- replaceAll(input = '', find = '', replace = '') {
- return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
- },
-
- // Convert to title case
- toTitleCase(input = '') {
- return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
- },
-
- // Convert string to pascalCase
- toPascalCase(input = '') {
- let string = input.toString();
-
- // Convert kebab case
- string = utils.replaceAll(string, '-', ' ');
-
- // Convert snake case
- string = utils.replaceAll(string, '_', ' ');
-
- // Convert to title case
- string = utils.toTitleCase(string);
-
- // Convert to pascal case
- return utils.replaceAll(string, ' ', '');
- },
-
- // Convert string to pascalCase
- toCamelCase(input = '') {
- let string = input.toString();
-
- // Convert to pascal case
- string = utils.toPascalCase(string);
-
- // Convert first character to lowercase
- return string.charAt(0).toLowerCase() + string.slice(1);
- },
-
- // Deep extend destination object with N more objects
- extend(target = {}, ...sources) {
- if (!sources.length) {
- return target;
- }
-
- const source = sources.shift();
-
- if (!utils.is.object(source)) {
- return target;
- }
-
- Object.keys(source).forEach(key => {
- if (utils.is.object(source[key])) {
- if (!Object.keys(target).includes(key)) {
- Object.assign(target, { [key]: {} });
- }
-
- utils.extend(target[key], source[key]);
- } else {
- Object.assign(target, { [key]: source[key] });
- }
- });
-
- return utils.extend(target, ...sources);
- },
-
- // Remove duplicates in an array
- dedupe(array) {
- if (!utils.is.array(array)) {
- return array;
- }
-
- return array.filter((item, index) => array.indexOf(item) === index);
- },
-
- // Clone nested objects
- cloneDeep(object) {
- return JSON.parse(JSON.stringify(object));
- },
-
- // Get a nested value in an object
- getDeep(object, path) {
- return path.split('.').reduce((obj, key) => obj && obj[key], object);
- },
-
- // Get the closest value in an array
- closest(array, value) {
- if (!utils.is.array(array) || !array.length) {
- return null;
- }
-
- return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
- },
-
- // Get the provider for a given URL
- getProviderByUrl(url) {
- // YouTube
- if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/.test(url)) {
- return providers.youtube;
- }
-
- // Vimeo
- if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) {
- return providers.vimeo;
- }
-
- return null;
- },
-
- // Parse YouTube ID from URL
- parseYouTubeId(url) {
- if (utils.is.empty(url)) {
- return null;
- }
-
- const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
- return url.match(regex) ? RegExp.$2 : url;
- },
-
- // Parse Vimeo ID from URL
- parseVimeoId(url) {
- if (utils.is.empty(url)) {
- return null;
- }
-
- if (utils.is.number(Number(url))) {
- return url;
- }
-
- const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
- return url.match(regex) ? RegExp.$2 : url;
- },
-
- // Convert a URL to a location object
- parseUrl(url) {
- const parser = document.createElement('a');
- parser.href = url;
- return parser;
- },
-
- // Get URL query parameters
- getUrlParams(input) {
- let search = input;
-
- // Parse URL if needed
- if (input.startsWith('http://') || input.startsWith('https://')) {
- ({ search } = utils.parseUrl(input));
- }
-
- if (utils.is.empty(search)) {
- return null;
- }
-
- const hashes = search.slice(search.indexOf('?') + 1).split('&');
-
- return hashes.reduce((params, hash) => {
- const [
- key,
- val,
- ] = hash.split('=');
-
- return Object.assign(params, { [key]: decodeURIComponent(val) });
- }, {});
- },
-
- // Convert object to URL parameters
- buildUrlParams(input) {
- if (!utils.is.object(input)) {
- return '';
- }
-
- return Object.keys(input)
- .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(input[key])}`)
- .join('&');
- },
-
- // Remove HTML from a string
- stripHTML(source) {
- const fragment = document.createDocumentFragment();
- const element = document.createElement('div');
- fragment.appendChild(element);
- element.innerHTML = source;
- return fragment.firstChild.innerText;
- },
-
- // Like outerHTML, but also works for DocumentFragment
- getHTML(element) {
- const wrapper = document.createElement('div');
- wrapper.appendChild(element);
- return wrapper.innerHTML;
- },
-
- // Get aspect ratio for dimensions
- getAspectRatio(width, height) {
- const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h));
- const ratio = getRatio(width, height);
- return `${width / ratio}:${height / ratio}`;
- },
-
- // Get the transition end event
- get transitionEndEvent() {
- const element = document.createElement('span');
-
- const events = {
- WebkitTransition: 'webkitTransitionEnd',
- MozTransition: 'transitionend',
- OTransition: 'oTransitionEnd otransitionend',
- transition: 'transitionend',
- };
-
- const type = Object.keys(events).find(event => element.style[event] !== undefined);
-
- return utils.is.string(type) ? events[type] : false;
- },
-
- // Force repaint of element
- repaint(element) {
- setTimeout(() => {
- utils.toggleHidden(element, true);
- element.offsetHeight; // eslint-disable-line
- utils.toggleHidden(element, false);
- }, 0);
- },
-};
-
-export default utils;