aframe
Version:
A web framework for building virtual reality experiences.
258 lines (223 loc) • 8.67 kB
JavaScript
import * as coordinates from '../utils/coordinates.js';
import debug from 'debug';
var warn = debug('core:propertyTypes:warn');
export var propertyTypes = {};
var nonCharRegex = /[,> .[\]:]/;
var urlRegex = /url\((.+)\)/;
// Built-in property types.
registerPropertyType('audio', '', assetParse, assetStringify);
registerPropertyType('array', [], arrayParse, arrayStringify, arrayEquals);
registerPropertyType('asset', '', assetParse, assetStringify);
registerPropertyType('boolean', false, boolParse);
registerPropertyType('color', '#FFF');
registerPropertyType('int', 0, intParse);
registerPropertyType('number', 0, numberParse);
registerPropertyType('map', '', assetParse, assetStringify);
registerPropertyType('model', '', assetParse, assetStringify);
registerPropertyType('selector', null, selectorParse, selectorStringify, defaultEquals, false);
registerPropertyType('selectorAll', null, selectorAllParse, selectorAllStringify, arrayEquals, false);
registerPropertyType('src', '', srcParse, assetStringify);
registerPropertyType('string', '');
registerPropertyType('time', 0, intParse);
registerPropertyType('vec2', {x: 0, y: 0}, vecParse, coordinates.stringify, coordinates.equals);
registerPropertyType('vec3', {x: 0, y: 0, z: 0}, vecParse, coordinates.stringify, coordinates.equals);
registerPropertyType('vec4', {x: 0, y: 0, z: 0, w: 1}, vecParse, coordinates.stringify, coordinates.equals);
/**
* 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 {any} [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.
* @param {function} [equals=defaultEquals] - Equality comparator.
* @param {boolean} [cacheable=false] - Whether or not the parsed value of a property can be cached.
*/
export function registerPropertyType (type, defaultValue, parse, stringify, equals, cacheable) {
if (type in propertyTypes) {
throw new Error('Property type ' + type + ' is already registered.');
}
propertyTypes[type] = {
default: defaultValue,
parse: parse || defaultParse,
stringify: stringify || defaultStringify,
equals: equals || defaultEquals,
isCacheable: cacheable !== false
};
}
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(', ');
}
function arrayEquals (a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) {
return a === b;
}
if (a.length !== b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
// FIXME: Deep-equals for objects?
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
/**
* 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 assetStringify (value) {
if (value.getAttribute) {
var id = value.getAttribute('id');
if (id) {
return '#' + value.getAttribute('id');
}
// HTMLElement without id can not be stringified, as there is no string assetParse
// could convert back to this exact element, using the src attribute instead.
return value.getAttribute('src');
}
return defaultStringify(value);
}
function defaultParse (value) {
return value;
}
function defaultStringify (value) {
if (value === null) { return 'null'; }
return value.toString();
}
function defaultEquals (a, b) {
return a === b;
}
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, defaultValue, target) {
return coordinates.parse(value, defaultValue, target);
}
/**
* 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.
*/
export 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;
}
/**
* 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.
*/
export 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;
}