Newer
Older
reroad-test / 2020-ryusei / aframe-master / src / utils / index.js
@ryusei ryusei on 22 Oct 2020 10 KB パノラマ表示
/* global location */

/* Centralized place to reference utilities since utils is exposed to the user. */
var debug = require('./debug');
var deepAssign = require('deep-assign');
var device = require('./device');
var objectAssign = require('object-assign');
var objectPool = require('./object-pool');

var warn = debug('utils:warn');

module.exports.bind = require('./bind');
module.exports.coordinates = require('./coordinates');
module.exports.debug = debug;
module.exports.device = device;
module.exports.entity = require('./entity');
module.exports.forceCanvasResizeSafariMobile = require('./forceCanvasResizeSafariMobile');
module.exports.isIE11 = require('./is-ie11');
module.exports.material = require('./material');
module.exports.objectPool = objectPool;
module.exports.split = require('./split').split;
module.exports.styleParser = require('./styleParser');
module.exports.trackedControls = require('./tracked-controls');

module.exports.checkHeadsetConnected = function () {
  warn('`utils.checkHeadsetConnected` has moved to `utils.device.checkHeadsetConnected`');
  return device.checkHeadsetConnected(arguments);
};
module.exports.isGearVR = module.exports.device.isGearVR = function () {
  warn('`utils.isGearVR` has been deprecated, use `utils.device.isMobileVR`');
};
module.exports.isIOS = function () {
  warn('`utils.isIOS` has moved to `utils.device.isIOS`');
  return device.isIOS(arguments);
};
module.exports.isOculusGo = module.exports.device.isOculusGo = function () {
  warn('`utils.isOculusGo` has been deprecated, use `utils.device.isMobileVR`');
};
module.exports.isMobile = function () {
  warn('`utils.isMobile has moved to `utils.device.isMobile`');
  return device.isMobile(arguments);
};

/**
 * Returns throttle function that gets called at most once every interval.
 *
 * @param {function} functionToThrottle
 * @param {number} minimumInterval - Minimal interval between calls (milliseconds).
 * @param {object} optionalContext - If given, bind function to throttle to this context.
 * @returns {function} Throttled function.
 */
module.exports.throttle = function (functionToThrottle, minimumInterval, optionalContext) {
  var lastTime;
  if (optionalContext) {
    functionToThrottle = module.exports.bind(functionToThrottle, optionalContext);
  }
  return function () {
    var time = Date.now();
    var sinceLastTime = typeof lastTime === 'undefined' ? minimumInterval : time - lastTime;
    if (typeof lastTime === 'undefined' || (sinceLastTime >= minimumInterval)) {
      lastTime = time;
      functionToThrottle.apply(null, arguments);
    }
  };
};

/**
 * Returns throttle function that gets called at most once every interval.
 * Uses the time/timeDelta timestamps provided by the global render loop for better perf.
 *
 * @param {function} functionToThrottle
 * @param {number} minimumInterval - Minimal interval between calls (milliseconds).
 * @param {object} optionalContext - If given, bind function to throttle to this context.
 * @returns {function} Throttled function.
 */
module.exports.throttleTick = function (functionToThrottle, minimumInterval, optionalContext) {
  var lastTime;
  if (optionalContext) {
    functionToThrottle = module.exports.bind(functionToThrottle, optionalContext);
  }
  return function (time, delta) {
    var sinceLastTime = typeof lastTime === 'undefined' ? delta : time - lastTime;
    if (typeof lastTime === 'undefined' || (sinceLastTime >= minimumInterval)) {
      lastTime = time;
      functionToThrottle(time, sinceLastTime);
    }
  };
};

/**
 * Returns debounce function that gets called only once after a set of repeated calls.
 *
 * @param {function} functionToDebounce
 * @param {number} wait - Time to wait for repeated function calls (milliseconds).
 * @param {boolean} immediate - Calls the function immediately regardless of if it should be waiting.
 * @returns {function} Debounced function.
 */
module.exports.debounce = function (func, wait, immediate) {
  var timeout;
  return function () {
    var context = this;
    var args = arguments;
    var later = function () {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
};

/**
 * Mix the properties of source object(s) into a destination object.
 *
 * @param  {object} dest - The object to which properties will be copied.
 * @param  {...object} source - The object(s) from which properties will be copied.
 */
module.exports.extend = objectAssign;
module.exports.extendDeep = deepAssign;

module.exports.clone = function (obj) {
  return JSON.parse(JSON.stringify(obj));
};

/**
 * Checks if two values are equal.
 * Includes objects and arrays and nested objects and arrays.
 * Try to keep this function performant as it will be called often to see if a component
 * should be updated.
 *
 * @param {object} a - First object.
 * @param {object} b - Second object.
 * @returns {boolean} Whether two objects are deeply equal.
 */
var deepEqual = (function () {
  var arrayPool = objectPool.createPool(function () { return []; });

  return function (a, b) {
    var key;
    var keysA;
    var keysB;
    var i;
    var valA;
    var valB;

    // If not objects or arrays, compare as values.
    if (a === undefined || b === undefined || a === null || b === null ||
        !(a && b && (a.constructor === Object && b.constructor === Object) ||
                    (a.constructor === Array && b.constructor === Array))) {
      return a === b;
    }

    // Different number of keys, not equal.
    keysA = arrayPool.use();
    keysB = arrayPool.use();
    keysA.length = 0;
    keysB.length = 0;
    for (key in a) { keysA.push(key); }
    for (key in b) { keysB.push(key); }
    if (keysA.length !== keysB.length) {
      arrayPool.recycle(keysA);
      arrayPool.recycle(keysB);
      return false;
    }

    // Return `false` at the first sign of inequality.
    for (i = 0; i < keysA.length; ++i) {
      valA = a[keysA[i]];
      valB = b[keysA[i]];
      // Check nested array and object.
      if ((typeof valA === 'object' || typeof valB === 'object') ||
          (Array.isArray(valA) && Array.isArray(valB))) {
        if (valA === valB) { continue; }
        if (!deepEqual(valA, valB)) {
          arrayPool.recycle(keysA);
          arrayPool.recycle(keysB);
          return false;
        }
      } else if (valA !== valB) {
        arrayPool.recycle(keysA);
        arrayPool.recycle(keysB);
        return false;
      }
    }

    arrayPool.recycle(keysA);
    arrayPool.recycle(keysB);
    return true;
  };
})();
module.exports.deepEqual = deepEqual;

/**
 * Computes the difference between two objects.
 *
 * @param {object} a - First object to compare (e.g., oldData).
 * @param {object} b - Second object to compare (e.g., newData).
 * @returns {object}
 *   Difference object where set of keys note which values were not equal, and values are
 *   `b`'s values.
 */
module.exports.diff = (function () {
  var keys = [];

  return function (a, b, targetObject) {
    var aVal;
    var bVal;
    var bKey;
    var diff;
    var key;
    var i;
    var isComparingObjects;

    diff = targetObject || {};

    // Collect A keys.
    keys.length = 0;
    for (key in a) { keys.push(key); }

    if (!b) { return diff; }

    // Collect B keys.
    for (bKey in b) {
      if (keys.indexOf(bKey) === -1) {
        keys.push(bKey);
      }
    }

    for (i = 0; i < keys.length; i++) {
      key = keys[i];
      aVal = a[key];
      bVal = b[key];
      isComparingObjects = aVal && bVal &&
                          aVal.constructor === Object && bVal.constructor === Object;
      if ((isComparingObjects && !deepEqual(aVal, bVal)) ||
          (!isComparingObjects && aVal !== bVal)) {
        diff[key] = bVal;
      }
    }
    return diff;
  };
})();

/**
 * Returns whether we should capture this keyboard event for keyboard shortcuts.
 * @param {Event} event Event object.
 * @returns {Boolean} Whether the key event should be captured.
 */
module.exports.shouldCaptureKeyEvent = function (event) {
  if (event.metaKey) { return false; }
  return document.activeElement === document.body;
};

/**
 * Splits a string into an array based on a delimiter.
 *
 * @param   {string=} [str='']        Source string
 * @param   {string=} [delimiter=' '] Delimiter to use
 * @returns {array}                   Array of delimited strings
 */
module.exports.splitString = function (str, delimiter) {
  if (typeof delimiter === 'undefined') { delimiter = ' '; }
  // First collapse the whitespace (or whatever the delimiter is).
  var regex = new RegExp(delimiter, 'g');
  str = (str || '').replace(regex, delimiter);
  // Then split.
  return str.split(delimiter);
};

/**
 * Extracts data from the element given an object that contains expected keys.
 *
 * @param {Element} Source element.
 * @param {Object} [defaults={}] Object of default key-value pairs.
 * @returns {Object}
 */
module.exports.getElData = function (el, defaults) {
  defaults = defaults || {};
  var data = {};
  Object.keys(defaults).forEach(copyAttribute);
  function copyAttribute (key) {
    if (el.hasAttribute(key)) {
      data[key] = el.getAttribute(key);
    }
  }
  return data;
};

/**
 * Retrieves querystring value.
 * @param  {String} name Name of querystring key.
 * @return {String}      Value
 */
module.exports.getUrlParameter = function (name) {
  // eslint-disable-next-line no-useless-escape
  name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  var results = regex.exec(location.search);
  return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};

/**
 * Detects whether context is within iframe.
 */
module.exports.isIframed = function () {
  return window.top !== window.self;
};

/**
 * Finds all elements under the element that have the isScene
 * property set to true
 */
module.exports.findAllScenes = function (el) {
  var matchingElements = [];
  var allElements = el.getElementsByTagName('*');
  for (var i = 0, n = allElements.length; i < n; i++) {
    if (allElements[i].isScene) {
      // Element exists with isScene set.
      matchingElements.push(allElements[i]);
    }
  }
  return matchingElements;
};

// Must be at bottom to avoid circular dependency.
module.exports.srcLoader = require('./src-loader');