Newer
Older
reroad-test / 2020-ryusei / aframe-master / src / core / propertyTypes.js
@ryusei ryusei on 22 Oct 2020 7 KB パノラマ表示
var coordinates = require('../utils/coordinates');
var debug = require('debug');

var error = debug('core:propertyTypes:warn');
var warn = debug('core:propertyTypes:warn');

var propertyTypes = module.exports.propertyTypes = {};
var nonCharRegex = /[,> .[\]:]/;
var urlRegex = /\url\((.+)\)/;

// Built-in property types.
registerPropertyType('audio', '', assetParse);
registerPropertyType('array', [], arrayParse, arrayStringify);
registerPropertyType('asset', '', assetParse);
registerPropertyType('boolean', false, boolParse);
registerPropertyType('color', '#FFF', defaultParse, defaultStringify);
registerPropertyType('int', 0, intParse);
registerPropertyType('number', 0, numberParse);
registerPropertyType('map', '', assetParse);
registerPropertyType('model', '', assetParse);
registerPropertyType('selector', null, selectorParse, selectorStringify);
registerPropertyType('selectorAll', null, selectorAllParse, selectorAllStringify);
registerPropertyType('src', '', srcParse);
registerPropertyType('string', '', defaultParse, defaultStringify);
registerPropertyType('time', 0, intParse);
registerPropertyType('vec2', {x: 0, y: 0}, vecParse, coordinates.stringify);
registerPropertyType('vec3', {x: 0, y: 0, z: 0}, vecParse, coordinates.stringify);
registerPropertyType('vec4', {x: 0, y: 0, z: 0, w: 1}, vecParse, coordinates.stringify);

/**
 * Register a parser for re-use such that when someone uses `type` in the schema,
 * `schema.process` will set the property `parse` and `stringify`.
 *
 * @param {string} type - Type name.
 * @param [defaultValue=null] -
 *   Default value to use if component does not define default value.
 * @param {function} [parse=defaultParse] - Parse string function.
 * @param {function} [stringify=defaultStringify] - Stringify to DOM function.
 */
function registerPropertyType (type, defaultValue, parse, stringify) {
  if ('type' in propertyTypes) {
    error('Property type ' + type + ' is already registered.');
    return;
  }

  propertyTypes[type] = {
    default: defaultValue,
    parse: parse || defaultParse,
    stringify: stringify || defaultStringify
  };
}
module.exports.registerPropertyType = registerPropertyType;

function arrayParse (value) {
  if (Array.isArray(value)) { return value; }
  if (!value || typeof value !== 'string') { return []; }
  return value.split(',').map(trim);
  function trim (str) { return str.trim(); }
}

function arrayStringify (value) {
  return value.join(', ');
}

/**
 * For general assets.
 *
 * @param {string} value - Can either be `url(<value>)`, an ID selector to an asset, or
 *   just string.
 * @returns {string} Parsed value from `url(<value>)`, src from `<someasset src>`, or
 *   just string.
 */
function assetParse (value) {
  var el;
  var parsedUrl;

  // If an element was provided (e.g. canvas or video), just return it.
  if (typeof value !== 'string') { return value; }

  // Wrapped `url()` in case of data URI.
  parsedUrl = value.match(urlRegex);
  if (parsedUrl) { return parsedUrl[1]; }

  // ID.
  if (value.charAt(0) === '#') {
    el = document.getElementById(value.substring(1));
    if (el) {
      // Pass through media elements. If we have the elements, we don't have to call
      // three.js loaders which would re-request the assets.
      if (el.tagName === 'CANVAS' || el.tagName === 'VIDEO' || el.tagName === 'IMG') {
        return el;
      }
      return el.getAttribute('src');
    }
    warn('"' + value + '" asset not found.');
    return;
  }

  // Non-wrapped url().
  return value;
}

function defaultParse (value) {
  return value;
}

function defaultStringify (value) {
  if (value === null) { return 'null'; }
  return value.toString();
}

function boolParse (value) {
  return value !== 'false' && value !== false;
}

function intParse (value) {
  return parseInt(value, 10);
}

function numberParse (value) {
  return parseFloat(value, 10);
}

function selectorParse (value) {
  if (!value) { return null; }
  if (typeof value !== 'string') { return value; }
  if (value[0] === '#' && !nonCharRegex.test(value)) {
    // When selecting element by ID only, use getElementById for better performance.
    // Don't match like #myId .child.
    return document.getElementById(value.substring(1));
  }
  return document.querySelector(value);
}

function selectorAllParse (value) {
  if (!value) { return null; }
  if (typeof value !== 'string') { return value; }
  return Array.prototype.slice.call(document.querySelectorAll(value), 0);
}

function selectorStringify (value) {
  if (value.getAttribute) {
    return '#' + value.getAttribute('id');
  }
  return defaultStringify(value);
}

function selectorAllStringify (value) {
  if (value instanceof Array) {
    return value.map(function (element) {
      return '#' + element.getAttribute('id');
    }).join(', ');
  }
  return defaultStringify(value);
}

function srcParse (value) {
  warn('`src` property type is deprecated. Use `asset` instead.');
  return assetParse(value);
}

function vecParse (value) {
  return coordinates.parse(value, this.default);
}

/**
 * Validate the default values in a schema to match their type.
 *
 * @param {string} type - Property type name.
 * @param defaultVal - Property type default value.
 * @returns {boolean} Whether default value is accurate given the type.
 */
function isValidDefaultValue (type, defaultVal) {
  if (type === 'audio' && typeof defaultVal !== 'string') { return false; }
  if (type === 'array' && !Array.isArray(defaultVal)) { return false; }
  if (type === 'asset' && typeof defaultVal !== 'string') { return false; }
  if (type === 'boolean' && typeof defaultVal !== 'boolean') { return false; }
  if (type === 'color' && typeof defaultVal !== 'string') { return false; }
  if (type === 'int' && typeof defaultVal !== 'number') { return false; }
  if (type === 'number' && typeof defaultVal !== 'number') { return false; }
  if (type === 'map' && typeof defaultVal !== 'string') { return false; }
  if (type === 'model' && typeof defaultVal !== 'string') { return false; }
  if (type === 'selector' && typeof defaultVal !== 'string' &&
      defaultVal !== null) { return false; }
  if (type === 'selectorAll' && typeof defaultVal !== 'string' &&
      defaultVal !== null) { return false; }
  if (type === 'src' && typeof defaultVal !== 'string') { return false; }
  if (type === 'string' && typeof defaultVal !== 'string') { return false; }
  if (type === 'time' && typeof defaultVal !== 'number') { return false; }
  if (type === 'vec2') { return isValidDefaultCoordinate(defaultVal, 2); }
  if (type === 'vec3') { return isValidDefaultCoordinate(defaultVal, 3); }
  if (type === 'vec4') { return isValidDefaultCoordinate(defaultVal, 4); }
  return true;
}
module.exports.isValidDefaultValue = isValidDefaultValue;

/**
 * Checks if default coordinates are valid.
 *
 * @param possibleCoordinates
 * @param {number} dimensions - 2 for 2D Vector, 3 for 3D vector.
 * @returns {boolean} Whether coordinates are parsed correctly.
 */
function isValidDefaultCoordinate (possibleCoordinates, dimensions) {
  if (possibleCoordinates === null) { return true; }
  if (typeof possibleCoordinates !== 'object') { return false; }

  if (Object.keys(possibleCoordinates).length !== dimensions) {
    return false;
  } else {
    var x = possibleCoordinates.x;
    var y = possibleCoordinates.y;
    var z = possibleCoordinates.z;
    var w = possibleCoordinates.w;

    if (typeof x !== 'number' || typeof y !== 'number') { return false; }
    if (dimensions > 2 && typeof z !== 'number') { return false; }
    if (dimensions > 3 && typeof w !== 'number') { return false; }
  }

  return true;
}
module.exports.isValidDefaultCoordinate = isValidDefaultCoordinate;