<commit_before>"use strict";
/**
 * @module opcua.address_space.types
 */

require("requirish")._(module);
var BinaryStreamSizeCalculator = require("lib/misc/binaryStream").BinaryStreamSizeCalculator;
var assert=require("assert");
var  _ = require("underscore");
var hexDump = require("lib/misc/utils").hexDump;

var getFactory = require("lib/misc/factories_factories").getFactory;
var _defaultTypeMap = require("lib/misc/factories_builtin_types")._defaultTypeMap;

/**
 * @class BaseUAObject
 * @constructor
 */
function BaseUAObject()
{

}

/**
 * Encode the object to the binary stream.
 * @class BaseUAObject
 * @method encode
 * @param stream {BinaryStream}
 * @param options {BinaryStream}
 */
BaseUAObject.prototype.encode = function(stream,options) {

};

/**
 * Decode the object from the binary stream.
 * @class BaseUAObject
 * @method decode
 * @param stream {BinaryStream}
 * @param options {Object}
 */
BaseUAObject.prototype.decode = function(stream,options) {

};

/**
 * Calculate the required size to store this object in a binary stream.
 * @method binaryStoreSize
 * @return {Number}
 */
BaseUAObject.prototype.binaryStoreSize = function (options) {

    var stream = new BinaryStreamSizeCalculator();
    this.encode(stream,options);
    return stream.length;
};

//var util = require("util");
//function w(s,w) {
//    return (s+"                                          ").substring(0,w);
//}
//
//function value_transform(str) {
//
//    var data;
//    var s = str.split("\n");
//    if (s.length ===1 ) {
//        data =  s[0];
//    } else {
//        data = s.join("\n   ");
//    }
//    return data;
//}
//function value_toString(value) {
//
//    var data;
//    if ( value instanceof Buffer) {
//
//        data = "\n"+ hexDump(value);
//
//    } else  if ( value instanceof Array ){
//        data = "ARRAY[\n";
//        var arr = [];
//        for (var i =0;i<value.length ; i++) {
//            arr.push(value_transform(value_toString(value[i])));
//        }
//        data += arr.join(",\n");
//        data += "\n]( length = " + value.length + " )";
//
//    } else if (value === null ) {
//        data = "<null>";
//    } else   if (typeof value === "string") {
//        data = '"' + value +'"' + "  (String)";
//    } else if (typeof value === "object") {
//        value = value.toString();
//        if (value === undefined ) {
//            data = "<undefined>";
//        } else if (value === null ) {
//            data = "<null>";
//        } else {
//            data = value_transform(value.toString());
//
//        }
//    } else {
//        data = value.toString();
//    }
//    return data;
//}
//function BaseUAObject_toString() {
//
//    var self = this;
//
//    var arr = Object.keys(self).map(function(key){
//        var value =  self[key];
//        var data = value_transform(value_toString(value));
//        return "\n"+ w(key + ": ",30) + data;
//    });
//    return arr.join();
//}

/**
 * @method toString
 * @return {String}
 */
BaseUAObject.prototype.toString = function () {

    var self = this;
    assert(self._schema);
    if (self._schema.hasOwnProperty("toString")) {

        return self._schema.toString.apply(self,arguments);

    } else {
        return self.explore();
        //xx return BaseUAObject_toString.apply(self,arguments);
    }
};

/**
 *
 * verify that all object attributes values are valid according to schema
 * @method isValid
 * @return {Boolean}
 */
BaseUAObject.prototype.isValid = function () {
    assert(this._schema);
    if (this._schema.toString) {
        return this._schema.isValid(this);
    } else {
        return true;
    }
};

function _decode_member_(value,field, stream, options) {

    var tracer = options.tracer;
    var cursor_before = stream.length;
    var fieldType = field.fieldType;

    if (field.category === "basic") {

        value = field.schema.decode(stream);
        tracer.trace("member", options.name, value, cursor_before, stream.length,fieldType);

    } else if (field.category === "enumeration") {

        value = field.schema.decode(stream);
        tracer.trace("member", options.name, value, cursor_before, stream.length,fieldType);

    } else  {
        assert(field.category === "complex");
        assert(_.isFunction(field.schema));
        value = new field.schema();
        value.decode_debug(stream, options);

    }
    return value;
}

/**
 * @method decode_debug
 *
 */
BaseUAObject.prototype.decode_debug = function (stream,options) {

    var tracer = options.tracer;
    var schema = this._schema;

    tracer.trace("start", options.name + "(" + schema.name + ")", stream.length, stream.length);
    var self = this;
    schema.fields.forEach(function(field){

        var value = self[field.name];

        if (field.isArray) {

            var cursor_before = stream.length;
            var nb = stream.readUInt32();
            if (nb === 0xFFFFFFFF) { nb = 0; }
            options.name = field.name + [];

            tracer.trace("start_array", field.name, nb, cursor_before, stream.length);
            for (var i = 0; i < nb; i++) {
                tracer.trace("start_element", field.name, i);
                options.name = "element #" + i;

                 _decode_member_(value,field, stream, options);

                tracer.trace("end_element", field.name, i);

            }
            tracer.trace("end_array", field.name, stream.length - 4);
        } else {

            options.name = field.name;
             _decode_member_(value,field, stream, options);

        }

    });

    tracer.trace("end", schema.name, stream.length, stream.length);
};

function r(str) {
    return (str + "                                ").substr(0, 30);
}

function apply_on_all_schema_fields(self, schema, data, callback) {

    assert(schema);
    var fields =schema.fields;
    var field, i ,n = fields.length;
    for( i =0; i< n ; i++ ) {
        field =fields[i];
        callback(self, field, data);
    }
}

var _exploreObject = function (self, field,  data) {

    if (!self) return;
    assert(self);

    var fieldType = field.fieldType;
    var fieldName = field.name;
    var category  = field.category;

    var padding = data.padding;

    var value = self[fieldName];

    var str;

    switch (category) {

        case "enumeration":
            str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value.key + " ( " + value.value + ")";
            data.lines.push(str);
            break;

        case "basic":
            if (value instanceof Buffer) {

                var _hexDump = hexDump(value);
                value = "<BUFFER>";
                data.lines.push(r(padding + fieldName, 30) + " " + r(fieldType, 15));
                data.lines.push(_hexDump);
            } else {
                if (fieldType === "IntegerId" || fieldType === "UInt32") {
                    value = "" + value + "               0x" + value.toString(16);
                }
                str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value;
                data.lines.push(str);
            }
            break;

        case "complex":
            if (field.subtype) {
                // this is a synonymous
                fieldType = field.subType;
                str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value;
                data.lines.push(str);
            } else {
                var _new_desc = getFactory(fieldType).prototype._schema;
                if (field.isArray) {
                    data.lines.push(r(padding + fieldName, 30) + r(fieldType, 15) + ': [');
                    var i = 0;
                    value.forEach(function (element) {
                        var data1 = { padding: padding + " ", lines: []};
                        apply_on_all_schema_fields(element, _new_desc, data1, _exploreObject);
                        data.lines.push(padding + i + ": {");
                        data.lines = data.lines.concat(data1.lines);
                        data.lines.push(padding + "}");
                        i++;
                    });

                    data.lines.push(r(padding + "", 30) + "]");
                } else {
                    data.lines.push(r(padding + fieldName, 30) + r(fieldType, 15) + "{");
                    var data1 = { padding: padding + "  ", lines: []};
                    apply_on_all_schema_fields(value, _new_desc, data1, _exploreObject);
                    data.lines = data.lines.concat(data1.lines);
                    data.lines.push(padding + "}");
                }
            }

            break;
        default:
            throw new Error("internal error: unknown kind_of_field");
    }
};

BaseUAObject.prototype.explore = function() {

    var self = this;
    var data = { padding: ' ', lines: []};
    data.lines.push('message /*' + this._schema.name + '*/ : {');
    apply_on_all_schema_fields(self, self._schema, data, _exploreObject);
    data.lines.push(" };");
    return data.lines.join("\n");
};

/**
 *
 * @method _get_base_schema
 * @param schema
 * @return {*}
 * @private
 */
function _get_base_schema(schema) {

    var base_schema = schema._base_schema;
    if (base_schema !== undefined) {
        return base_schema;
    }

    base_schema = null;
    if (schema.baseType && schema.baseType !== "BaseUAObject" ) {
        var baseType = getFactory(schema.baseType);
        if (!baseType) {
            console.log(schema.name + " cannot find schema for base type ",schema.baseType);
            assert(baseType);
        }
        if (baseType.prototype._schema) {
            base_schema = baseType.prototype._schema;
        }
    }
    // put in  cache for speedup
    schema._base_schema = base_schema;

    return base_schema;
}
function _visit_schema_chain(self,schema,options,func, extra_data) {
    assert(_.isFunction(func));

    // apply also construct to baseType schema first
    var base_schema = _get_base_schema(schema);
    if (base_schema) {
        _visit_schema_chain(self,base_schema,options,func,extra_data);
    }
    func.call(self,schema,options,extra_data);
}

function _JSONify(schema, options) {

    var self = this;
    schema.fields.forEach(function (field) {
        var f = self[field.name];
        if (f === null || f === undefined) {
            return;
        }

        var t = _defaultTypeMap[field.fieldType];

        function jsonify(value) {

            if (_.isFunction(field.toJSON)) {
                return field.toJSON(value);
            } else if (t && t.toJSON) {
                return t.toJSON(value);
            } else if (value.toJSON) {
                return value.toJSON();
            } else {
                return f;
            }

        }

        if (field.isArray) {
            options[field.name] =  f.map(jsonify);
        } else {
            options[field.name] =  jsonify(f);
        }
    });
}

BaseUAObject.prototype.toJSON = function () {

    var self = this;

    assert(this._schema);
    if (this._schema.toJSON) {
        return this._schema.toJSON.apply(this,arguments);
    } else {
        //xx return Object.toJSON.apply(this,arguments);
        assert(self._schema);
        var schema =self._schema;
        var options = {};
        _visit_schema_chain(self,schema,options,_JSONify);
        return options;
    }
};

exports.BaseUAObject = BaseUAObject;<commit_msg>code cleanup<commit_after>"use strict";
/**
 * @module opcua.address_space.types
 */

require("requirish")._(module);
var BinaryStreamSizeCalculator = require("lib/misc/binaryStream").BinaryStreamSizeCalculator;
var assert=require("assert");
var  _ = require("underscore");
var hexDump = require("lib/misc/utils").hexDump;

var getFactory = require("lib/misc/factories_factories").getFactory;
var _defaultTypeMap = require("lib/misc/factories_builtin_types")._defaultTypeMap;

var get_base_schema =  require("lib/misc/factories_schema_helpers").get_base_schema;

    /**
 * @class BaseUAObject
 * @constructor
 */
function BaseUAObject()
{

}

/**
 * Encode the object to the binary stream.
 * @class BaseUAObject
 * @method encode
 * @param stream {BinaryStream}
 * @param options {BinaryStream}
 */
BaseUAObject.prototype.encode = function(stream,options) {

};

/**
 * Decode the object from the binary stream.
 * @class BaseUAObject
 * @method decode
 * @param stream {BinaryStream}
 * @param options {Object}
 */
BaseUAObject.prototype.decode = function(stream,options) {

};

/**
 * Calculate the required size to store this object in a binary stream.
 * @method binaryStoreSize
 * @return {Number}
 */
BaseUAObject.prototype.binaryStoreSize = function (options) {

    var stream = new BinaryStreamSizeCalculator();
    this.encode(stream,options);
    return stream.length;
};

//var util = require("util");
//function w(s,w) {
//    return (s+"                                          ").substring(0,w);
//}
//
//function value_transform(str) {
//
//    var data;
//    var s = str.split("\n");
//    if (s.length ===1 ) {
//        data =  s[0];
//    } else {
//        data = s.join("\n   ");
//    }
//    return data;
//}
//function value_toString(value) {
//
//    var data;
//    if ( value instanceof Buffer) {
//
//        data = "\n"+ hexDump(value);
//
//    } else  if ( value instanceof Array ){
//        data = "ARRAY[\n";
//        var arr = [];
//        for (var i =0;i<value.length ; i++) {
//            arr.push(value_transform(value_toString(value[i])));
//        }
//        data += arr.join(",\n");
//        data += "\n]( length = " + value.length + " )";
//
//    } else if (value === null ) {
//        data = "<null>";
//    } else   if (typeof value === "string") {
//        data = '"' + value +'"' + "  (String)";
//    } else if (typeof value === "object") {
//        value = value.toString();
//        if (value === undefined ) {
//            data = "<undefined>";
//        } else if (value === null ) {
//            data = "<null>";
//        } else {
//            data = value_transform(value.toString());
//
//        }
//    } else {
//        data = value.toString();
//    }
//    return data;
//}
//function BaseUAObject_toString() {
//
//    var self = this;
//
//    var arr = Object.keys(self).map(function(key){
//        var value =  self[key];
//        var data = value_transform(value_toString(value));
//        return "\n"+ w(key + ": ",30) + data;
//    });
//    return arr.join();
//}

/**
 * @method toString
 * @return {String}
 */
BaseUAObject.prototype.toString = function () {

    var self = this;
    assert(self._schema);
    if (self._schema.hasOwnProperty("toString")) {

        return self._schema.toString.apply(self,arguments);

    } else {
        return self.explore();
        //xx return BaseUAObject_toString.apply(self,arguments);
    }
};

/**
 *
 * verify that all object attributes values are valid according to schema
 * @method isValid
 * @return {Boolean}
 */
BaseUAObject.prototype.isValid = function () {
    assert(this._schema);
    if (this._schema.toString) {
        return this._schema.isValid(this);
    } else {
        return true;
    }
};

function _decode_member_(value,field, stream, options) {

    var tracer = options.tracer;
    var cursor_before = stream.length;
    var fieldType = field.fieldType;

    if (field.category === "basic") {

        value = field.schema.decode(stream);
        tracer.trace("member", options.name, value, cursor_before, stream.length,fieldType);

    } else if (field.category === "enumeration") {

        value = field.schema.decode(stream);
        tracer.trace("member", options.name, value, cursor_before, stream.length,fieldType);

    } else  {
        assert(field.category === "complex");
        assert(_.isFunction(field.schema));
        value = new field.schema();
        value.decode_debug(stream, options);

    }
    return value;
}

/**
 * @method decode_debug
 *
 */
BaseUAObject.prototype.decode_debug = function (stream,options) {

    var tracer = options.tracer;
    var schema = this._schema;

    tracer.trace("start", options.name + "(" + schema.name + ")", stream.length, stream.length);
    var self = this;
    schema.fields.forEach(function(field){

        var value = self[field.name];

        if (field.isArray) {

            var cursor_before = stream.length;
            var nb = stream.readUInt32();
            if (nb === 0xFFFFFFFF) { nb = 0; }
            options.name = field.name + [];

            tracer.trace("start_array", field.name, nb, cursor_before, stream.length);
            for (var i = 0; i < nb; i++) {
                tracer.trace("start_element", field.name, i);
                options.name = "element #" + i;

                 _decode_member_(value,field, stream, options);

                tracer.trace("end_element", field.name, i);

            }
            tracer.trace("end_array", field.name, stream.length - 4);
        } else {

            options.name = field.name;
             _decode_member_(value,field, stream, options);

        }

    });

    tracer.trace("end", schema.name, stream.length, stream.length);
};

function r(str) {
    return (str + "                                ").substr(0, 30);
}

function apply_on_all_schema_fields(self, schema, data, callback) {

    assert(schema);
    var fields =schema.fields;
    var field, i ,n = fields.length;
    for( i =0; i< n ; i++ ) {
        field =fields[i];
        callback(self, field, data);
    }
}

var _exploreObject = function (self, field,  data) {

    if (!self) return;
    assert(self);

    var fieldType = field.fieldType;
    var fieldName = field.name;
    var category  = field.category;

    var padding = data.padding;

    var value = self[fieldName];

    var str;

    switch (category) {

        case "enumeration":
            str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value.key + " ( " + value.value + ")";
            data.lines.push(str);
            break;

        case "basic":
            if (value instanceof Buffer) {

                var _hexDump = hexDump(value);
                value = "<BUFFER>";
                data.lines.push(r(padding + fieldName, 30) + " " + r(fieldType, 15));
                data.lines.push(_hexDump);
            } else {
                if (fieldType === "IntegerId" || fieldType === "UInt32") {
                    value = "" + value + "               0x" + value.toString(16);
                }
                str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value;
                data.lines.push(str);
            }
            break;

        case "complex":
            if (field.subtype) {
                // this is a synonymous
                fieldType = field.subType;
                str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value;
                data.lines.push(str);
            } else {
                var _new_desc = getFactory(fieldType).prototype._schema;
                if (field.isArray) {
                    data.lines.push(r(padding + fieldName, 30) + r(fieldType, 15) + ': [');
                    var i = 0;
                    value.forEach(function (element) {
                        var data1 = { padding: padding + " ", lines: []};
                        apply_on_all_schema_fields(element, _new_desc, data1, _exploreObject);
                        data.lines.push(padding + i + ": {");
                        data.lines = data.lines.concat(data1.lines);
                        data.lines.push(padding + "}");
                        i++;
                    });

                    data.lines.push(r(padding + "", 30) + "]");
                } else {
                    data.lines.push(r(padding + fieldName, 30) + r(fieldType, 15) + "{");
                    var data1 = { padding: padding + "  ", lines: []};
                    apply_on_all_schema_fields(value, _new_desc, data1, _exploreObject);
                    data.lines = data.lines.concat(data1.lines);
                    data.lines.push(padding + "}");
                }
            }

            break;
        default:
            throw new Error("internal error: unknown kind_of_field");
    }
};

BaseUAObject.prototype.explore = function() {

    var self = this;
    var data = { padding: ' ', lines: []};
    data.lines.push('message /*' + this._schema.name + '*/ : {');
    apply_on_all_schema_fields(self, self._schema, data, _exploreObject);
    data.lines.push(" };");
    return data.lines.join("\n");
};

function _visit_schema_chain(self,schema,options,func, extra_data) {
    assert(_.isFunction(func));

    // apply also construct to baseType schema first
    var base_schema = get_base_schema(schema);
    if (base_schema) {
        _visit_schema_chain(self,base_schema,options,func,extra_data);
    }
    func.call(self,schema,options,extra_data);
}

function _JSONify(schema, options) {

    var self = this;
    schema.fields.forEach(function (field) {
        var f = self[field.name];
        if (f === null || f === undefined) {
            return;
        }

        var t = _defaultTypeMap[field.fieldType];

        function jsonify(value) {

            if (_.isFunction(field.toJSON)) {
                return field.toJSON(value);
            } else if (t && t.toJSON) {
                return t.toJSON(value);
            } else if (value.toJSON) {
                return value.toJSON();
            } else {
                return f;
            }

        }

        if (field.isArray) {
            options[field.name] =  f.map(jsonify);
        } else {
            options[field.name] =  jsonify(f);
        }
    });
}

BaseUAObject.prototype.toJSON = function () {

    var self = this;

    assert(this._schema);
    if (this._schema.toJSON) {
        return this._schema.toJSON.apply(this,arguments);
    } else {
        //xx return Object.toJSON.apply(this,arguments);
        assert(self._schema);
        var schema =self._schema;
        var options = {};
        _visit_schema_chain(self,schema,options,_JSONify);
        return options;
    }
};

exports.BaseUAObject = BaseUAObject;<|endoftext|>


<commit_before>const path = require('path')
const glob = require('globby')
const cwd = path.join(__dirname, '..')
const { parse } = require('vue-docgen-api')

module.exports = async () => {
	const docFiles = glob.sync('components/**/*.md', { cwd }).map((f) => '/' + f)
	const components = await Promise.all(
		glob.sync('../src/components/**/*.{vue,js,jsx,ts,tsx}', { cwd, absolute: true }).map(async (path) => {
			return {
				name: (await parse(path)).displayName.replace(/[^a-zA-Z0-9_]/g, ''),
				path
			}
		})
	)

	return {
		dest: path.join(__dirname, '../../dist'),
		title: 'VuePress DocGen Live',
		themeConfig: {
			search: false,
			sidebar: docFiles
		},
		plugins: [
			['live'],
			[
				'@vuepress/register-components',
				{
					components
				}
			]
		]
	}
}
<commit_msg>docs: repair docgen example<commit_after>const path = require('path')
const glob = require('globby')
const cwd = path.join(__dirname, '..')
const { parse } = require('vue-docgen-api')

module.exports = async () => {
	const docFiles = glob.sync('components/**/*.md', { cwd }).map((f) => '/' + f)
	const components = await Promise.all(
		glob.sync('../src/components/**/*.{vue,js,jsx,ts,tsx}', { cwd, absolute: true }).map(async (path) => {
			return {
				name: (await parse(path)).displayName.replace(/[^a-zA-Z0-9_]/g, ''),
				path
			}
		})
	)

	return {
		base: "/docgen/",
		dest: path.join(__dirname, '../../dist'),
		title: 'VuePress DocGen Live',
		themeConfig: {
			search: false,
			sidebar: docFiles
		},
		plugins: [
			['live'],
			[
				'@vuepress/register-components',
				{
					components
				}
			]
		]
	}
}
<|endoftext|>


<commit_before>/**
 * @author thespite / http://clicktorelease.com/
 */

THREE.ImageBitmapLoader = function (manager) {

	this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager;
	this.options = {};

};

THREE.ImageBitmapLoader.prototype = {

	constructor: THREE.ImageBitmapLoader,

	setOptions: function setOptions(options) {

		this.options = options;
		return this;

	},

	load: function load(url, onLoad, onProgress, onError) {

		if (url === undefined) url = '';

		if (this.path !== undefined) url = this.path + url;

		var scope = this;

		var cached = THREE.Cache.get(url);

		if (cached !== undefined) {

			scope.manager.itemStart(url);

			setTimeout(function () {

				if (onLoad) onLoad(cached);

				scope.manager.itemEnd(url);

			}, 0);

			return cached;
		}

		fetch(url).then(function (res) {

			return res.blob();

		}).then(function (res) {

			return createImageBitmap(res, scope.options);

		}).then(function (res) {

			THREE.Cache.add(url, res);

			if (onLoad) onLoad(res);

			scope.manager.itemEnd(url);

		}).catch(function (e) {

			if (onError) onError(e);

			scope.manager.itemEnd(url);
			scope.manager.itemError(url);

		});
	}

};
<commit_msg>added detection code<commit_after>/**
 * @author thespite / http://clicktorelease.com/
 */

 function detectCreateImageBitmap() {

	var url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';

	return new Promise(function (resolve, reject) {

		if (!('createImageBitmap' in window)) {
			reject();
			return;
		}

		fetch(url).then(function (res) {
			return res.blob();
		}).then(function (blob) {
			Promise.all([
				createImageBitmap(blob, { imageOrientation: "none", premultiplyAlpha: "none" }),
				createImageBitmap(blob, { imageOrientation: "flipY", premultiplyAlpha: "none" }),
				createImageBitmap(blob, { imageOrientation: "none", premultiplyAlpha: "premultiply" }),
				createImageBitmap(blob, { imageOrientation: "flipY", premultiplyAlpha: "premultiply" })
			]).then(function (res) {
				resolve();
			}).catch(function (e) {
				reject();
			});
		});
	});

}

var canUseImageBitmap = detectCreateImageBitmap();
canUseImageBitmap
.then( function( res ) {
	console.log( 'createImageBitmap supported' );
})
.catch( function( res ) {
	console.log( 'createImageBitmap not supported' );
});

THREE.ImageBitmapLoader = function (manager) {

	this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager;
	this.options = {};

};

THREE.ImageBitmapLoader.prototype = {

	constructor: THREE.ImageBitmapLoader,

	setOptions: function setOptions(options) {

		this.options = options;
		return this;

	},

	load: function load(url, onLoad, onProgress, onError) {

		if (url === undefined) url = '';

		if (this.path !== undefined) url = this.path + url;

		var scope = this;

		var cached = THREE.Cache.get(url);

		if (cached !== undefined) {

			scope.manager.itemStart(url);

			setTimeout(function () {

				if (onLoad) onLoad(cached);

				scope.manager.itemEnd(url);

			}, 0);

			return cached;
		}

		fetch(url).then(function (res) {

			return res.blob();

		}).then(function (res) {

			return createImageBitmap(res, scope.options);

		}).then(function (res) {

			THREE.Cache.add(url, res);

			if (onLoad) onLoad(res);

			scope.manager.itemEnd(url);

		}).catch(function (e) {

			if (onError) onError(e);

			scope.manager.itemEnd(url);
			scope.manager.itemError(url);

		});
	}

};
<|endoftext|>


<commit_before>define([
	'goo/statemachine/actions/Action',
	'goo/renderer/Camera',
	'goo/renderer/bounds/BoundingSphere'
],
/** @lends */
function(
	Action,
	Camera,
	BoundingSphere
) {
	'use strict';

	function InFrustumAction(/*id, settings*/) {
		Action.apply(this, arguments);
	}

	InFrustumAction.prototype = Object.create(Action.prototype);
	InFrustumAction.prototype.constructor = InFrustumAction;

	InFrustumAction.external = {
		name: 'In Frustum',
		description: 'Performs a transition based on whether the entity is in a camera\'s frustum or not',
		canTransition: true,
		parameters: [{
			name: 'Current camera',
			key: 'current',
			type: 'boolean',
			description: 'Check this to always use the current camera',
			'default': true
		}, {
			name: 'Camera',
			key: 'cameraEntityRef',
			type: 'cameraEntity',
			description: 'Other camera; Will be ignored if the previous option is checked',
			'default': null
		}, {
			name: 'On every frame',
			key: 'everyFrame',
			type: 'boolean',
			description: 'Repeat this action every frame',
			'default': true
		}],
		transitions: [{
			key: 'inside',
			name: 'Inside',
			description: 'State to transition to if entity is in the frustum'
		}, {
			key: 'outside',
			name: 'Outside',
			description: 'State to transition to if entity is outside the frustum'
		}]
	};

	InFrustumAction.prototype._setup = function (fsm) {
		if (!this.current) {
			var world = fsm.getOwnerEntity()._world;
			var cameraEntity = world.entityManager.getEntityByName(this.cameraEntityRef);
			this.camera = cameraEntity.cameraComponent.camera;
		}
	};

	InFrustumAction.prototype._run = function (fsm) {
		var entity = fsm.getOwnerEntity();

		if (this.current) {
			if (entity.isVisible) {
				fsm.send(this.transitions.inside);
			} else {
				fsm.send(this.transitions.outside);
			}
		} else {
			var boundingVolume = entity.meshDataComponent ? entity.meshDataComponent.modelBound : new BoundingSphere(entity.transformComponent.worldTransform.translation, 0.001);
			if (this.camera.contains(boundingVolume) === Camera.Outside) {
				fsm.send(this.transitions.outside);
			} else {
				fsm.send(this.transitions.inside);
			}
		}
	};

	return InFrustumAction;
});<commit_msg>#451 Fixed bug in InFrustum action<commit_after>define([
	'goo/statemachine/actions/Action',
	'goo/renderer/Camera',
	'goo/renderer/bounds/BoundingSphere'
],
/** @lends */
function(
	Action,
	Camera,
	BoundingSphere
) {
	'use strict';

	function InFrustumAction(/*id, settings*/) {
		Action.apply(this, arguments);
	}

	InFrustumAction.prototype = Object.create(Action.prototype);
	InFrustumAction.prototype.constructor = InFrustumAction;

	InFrustumAction.external = {
		name: 'In Frustum',
		description: 'Performs a transition based on whether the entity is in a camera\'s frustum or not',
		canTransition: true,
		parameters: [{
			name: 'Current camera',
			key: 'current',
			type: 'boolean',
			description: 'Check this to always use the current camera',
			'default': true
		}, {
			name: 'Camera',
			key: 'cameraEntityRef',
			type: 'cameraEntity',
			description: 'Other camera; Will be ignored if the previous option is checked',
			'default': null
		}, {
			name: 'On every frame',
			key: 'everyFrame',
			type: 'boolean',
			description: 'Repeat this action every frame',
			'default': true
		}],
		transitions: [{
			key: 'inside',
			name: 'Inside',
			description: 'State to transition to if entity is in the frustum'
		}, {
			key: 'outside',
			name: 'Outside',
			description: 'State to transition to if entity is outside the frustum'
		}]
	};

	InFrustumAction.prototype._setup = function (fsm) {
		if (!this.current) {
			var world = fsm.getOwnerEntity()._world;
			var cameraEntity = world.entityManager.getEntityByName(this.cameraEntityRef);
			this.camera = cameraEntity.cameraComponent.camera;
		}
	};

	InFrustumAction.prototype._run = function (fsm) {
		var entity = fsm.getOwnerEntity();

		if (this.current) {
			if (entity.isVisible) {
				fsm.send(this.transitions.inside);
			} else {
				fsm.send(this.transitions.outside);
			}
		} else {
			var boundingVolume = entity.meshRendererComponent ? entity.meshRendererComponent.worldBound : new BoundingSphere(entity.transformComponent.worldTransform.translation, 0.001);
			if (this.camera.contains(boundingVolume) === Camera.Outside) {
				fsm.send(this.transitions.outside);
			} else {
				fsm.send(this.transitions.inside);
			}
		}
	};

	return InFrustumAction;
});<|endoftext|>


<commit_before>/*
* Copyright (c) 2012-2015 S-Core Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
 * Constructor
 * DocumentManager
 *
 * @see
 * @since: 2015.07.15
 * @author: hw.shim
 */

// @formatter:off
define([
    'webida-lib/util/genetic',
    'webida-lib/util/logger/logger-client',
    'webida-lib/plugins/workbench/ui/ModelManager',
    './Document'
], function(
    genetic,
    Logger,
    ModelManager,
    Document
) {
    'use strict';
// @formatter:on

    /**
     * @typedef {Object} DataSource
     * @typedef {Object} Document
     */

    var logger = new Logger();
    //logger.setConfig('level', Logger.LEVELS.log);
    //logger.off();

    function DocumentManager(dataSource) {
        logger.info('new DocumentManager(' + dataSource + ')');

        ModelManager.apply(this, arguments);
    }

    genetic.inherits(DocumentManager, ModelManager, {

        /**
         * Creates a Document
         *
         * @param {DocumentManager~createModelCallback} callback
         */
        /**
         * @callback DocumentManager~createModelCallback
         * @param {Document} doc
         */
        createModel: function(callback) {
        	logger.info('createModel(callback)');
            var that = this;
            var dataSource = this.getDataSource();
            dataSource.getContents(function(contents) {
                that.savedContents = contents;
                //Creates a Document(Model) from a DataSource
                var doc = new Document(contents);
                that.setModel(doc);
                callback(doc);
            });
        },

        /**
         * Saves a PartModel to a Persistence
         *
         * @param {DocumentManager~saveModelCallback} callback
         * TODO : Consider Memento Pattern
         */
        /**
         * @callback DocumentManager~saveModelCallback
         * @param {Document} doc
         */
        saveModel: function(callback) {
            var that = this;
            var doc = this.getModel();
            var dataSource = this.getDataSource();
            dataSource.setContents(doc.getText(), function(contents) {
                that.savedContents = contents;
                callback();
            });
        },

        /**
         * Resets the given document to its last saved state.
         */
        resetModel: function() {
            var doc = this.getModel();
            doc.setText(this.savedContents);
        },

        /**
         * Returns whether the PartModel can be saved or not.
         * This method compares between the Document's text
         * and Persistence(File)'s contents
         */
        canSaveModel: function() {
            if (this.getModel()) {
                var doc = this.getModel();
                var dataSource = this.getDataSource();
                var persistence = dataSource.getPersistence();
                if (doc.getText() === persistence.getContents()) {
                    return false;
                } else {
                    return true;
                }
            } else {
                return false;
            }
        },

        /**
         * @param {string} contents
         * @param {Object} caller
         */
        setContents: function(contents, caller) {
            var doc = this.getModel();
            if (contents !== doc.getText()) {
                doc.update(contents, caller);
            }
        },

        getContents: function() {
            var doc = this.getModel();
            if (doc) {
                return doc.getText();
            } else {
                throw new Error('Document has not been set yet');
            }
        }
    });

    return DocumentManager;
});
<commit_msg>[TASK] DocumentManager : emit PartModel.READY event<commit_after>/*
* Copyright (c) 2012-2015 S-Core Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
 * Constructor
 * DocumentManager
 *
 * @see
 * @since: 2015.07.15
 * @author: hw.shim
 */

// @formatter:off
define([
    'dojo/topic',
    'webida-lib/util/genetic',
    'webida-lib/util/logger/logger-client',
    'webida-lib/plugins/workbench/ui/ModelManager',
    'webida-lib/plugins/workbench/ui/PartModel',
    './Document'
], function(
    topic,
    genetic,
    Logger,
    ModelManager,
    PartModel,
    Document
) {
    'use strict';
// @formatter:on

    /**
     * @typedef {Object} DataSource
     * @typedef {Object} Document
     */

    var logger = new Logger();
    //logger.setConfig('level', Logger.LEVELS.log);
    //logger.off();

    function DocumentManager(dataSource) {
        logger.info('new DocumentManager(' + dataSource + ')');

        ModelManager.apply(this, arguments);
    }

    genetic.inherits(DocumentManager, ModelManager, {

        /**
         * Creates a Document
         *
         * @param {DocumentManager~createModelCallback} callback
         * @return {Document}
         */
        /**
         * @callback DocumentManager~createModelCallback
         * @param {Document} doc
         */
        createModel: function(callback) {
            logger.info('createModel(callback)');
            var that = this;
            var doc = new Document();
            var dataSource = this.getDataSource();
            dataSource.getContents(function(contents) {
                doc.setText(contents);
                that.setModel(doc);
                that.savedContents = contents;
                callback(doc);
                //Let's give a chance to this doc
                //that it can register READY event in advance
                //In case of synchronous getContents()
                //See FileDataSource > getContents()'s else block
                setTimeout(function() {
                    doc.emit(PartModel.READY, doc);
                });
            });
            return doc;
        },

        /**
         * Saves a PartModel to a Persistence
         *
         * @param {DocumentManager~saveModelCallback} callback
         * TODO : Consider Memento Pattern
         */
        /**
         * @callback DocumentManager~saveModelCallback
         * @param {Document} doc
         */
        saveModel: function(callback) {
            var that = this;
            var doc = this.getModel();
            var dataSource = this.getDataSource();
            dataSource.setContents(doc.getText(), function(contents) {
                that.savedContents = contents;
                callback();
            });
        },

        /**
         * Resets the given document to its last saved state.
         */
        resetModel: function() {
            var doc = this.getModel();
            doc.setText(this.savedContents);
        },

        /**
         * Returns whether the PartModel can be saved or not.
         * This method compares between the Document's text
         * and Persistence(File)'s contents
         */
        canSaveModel: function() {
            if (this.getModel()) {
                var doc = this.getModel();
                var dataSource = this.getDataSource();
                var persistence = dataSource.getPersistence();
                if (doc.getText() === persistence.getContents()) {
                    return false;
                } else {
                    return true;
                }
            } else {
                return false;
            }
        },

        /**
         * @param {string} contents
         * @param {Object} caller
         */
        setContents: function(contents, caller) {
            var doc = this.getModel();
            if (contents !== doc.getText()) {
                doc.setContents(contents, caller);
            }
        },

        getContents: function() {
            var doc = this.getModel();
            if (doc) {
                return doc.getText();
            } else {
                throw new Error('Document has not been set yet');
            }
        }
    });

    return DocumentManager;
});
<|endoftext|>


<commit_before>'use strict';

var chalk   = require('chalk');
var path    = require('path');
var Promise = require('../ext/promise');
var assign  = require('ember-cli-lodash-subset').assign;

function AssetPrinterSize(options) {
  assign(this, options);
}

AssetPrinterSize.prototype.print = function () {
  var filesize = require('filesize');
  var ui = this.ui;

  return this.makeAssetSizesObject().then(function (files) {
    this.validateAssetPath(files);

    ui.writeLine(chalk.green('File sizes:'));
    return files.forEach(function (file) {
      var sizeOutput = filesize(file.size);
      if (file.showGzipped) {
        sizeOutput += ' (' + filesize(file.gzipSize) + ' gzipped)';
      }

      ui.writeLine(chalk.blue(' - ' + file.name + ': ') + chalk.white(sizeOutput));
    });

  }.bind(this));
};

AssetPrinterSize.prototype.makeAssetSizesObject = function () {
  var fs = require('fs');
  var zlib = require('zlib');
  var gzip = Promise.denodeify(zlib.gzip);
  var files = this.makeFileGlob();
  var testFileRegex = /(test-(loader|support))|(testem)/i;

  var assets = files
    // Skip test files
    .filter(function (file) {
      var filename = path.basename(file);
      return !testFileRegex.test(filename);
    })
    // Print human-readable file sizes (including gzipped)
    .map(function (file) {
      var filename = path.basename(file);
      var contentsBuffer = fs.readFileSync(file);
      return gzip(contentsBuffer).then(function (buffer) {
        return {
          name: filename,
          size: contentsBuffer.length,
          gzipSize: buffer.length,
          showGzipped: contentsBuffer.length > 0
        };
      });
    });

  return Promise.all(assets);
};

AssetPrinterSize.prototype.makeFileGlob = function () {
  var glob = require('glob');
  var outputPath = this.outputPath;
  var globOptions = {nodir: true};

  return glob.sync(outputPath + '/**/*.{css,js}', globOptions);
};

AssetPrinterSize.prototype.validateAssetPath = function (files) {
  if (!files.length) {
    throw new Error('No asset files found in the path provided: ' + this.outputPath);
  }
};

module.exports = AssetPrinterSize;
<commit_msg>Auto merge of #6296 - josemarluedke:jl-asset-sizes-relative-path, r=nathanhammond<commit_after>'use strict';

var chalk   = require('chalk');
var path    = require('path');
var Promise = require('../ext/promise');
var assign  = require('ember-cli-lodash-subset').assign;

function AssetPrinterSize(options) {
  assign(this, options);
}

AssetPrinterSize.prototype.print = function () {
  var filesize = require('filesize');
  var ui = this.ui;

  return this.makeAssetSizesObject().then(function (files) {
    this.validateAssetPath(files);

    ui.writeLine(chalk.green('File sizes:'));
    return files.forEach(function (file) {
      var sizeOutput = filesize(file.size);
      if (file.showGzipped) {
        sizeOutput += ' (' + filesize(file.gzipSize) + ' gzipped)';
      }

      ui.writeLine(chalk.blue(' - ' + file.name + ': ') + chalk.white(sizeOutput));
    });

  }.bind(this));
};

AssetPrinterSize.prototype.makeAssetSizesObject = function () {
  var fs = require('fs');
  var zlib = require('zlib');
  var gzip = Promise.denodeify(zlib.gzip);
  var files = this.makeFileGlob();
  var testFileRegex = /(test-(loader|support))|(testem)/i;

  var assets = files
    // Skip test files
    .filter(function (file) {
      var filename = path.basename(file);
      return !testFileRegex.test(filename);
    })
    // Print human-readable file sizes (including gzipped)
    .map(function (file) {
      var contentsBuffer = fs.readFileSync(file);
      return gzip(contentsBuffer).then(function (buffer) {
        return {
          name: file,
          size: contentsBuffer.length,
          gzipSize: buffer.length,
          showGzipped: contentsBuffer.length > 0
        };
      });
    });

  return Promise.all(assets);
};

AssetPrinterSize.prototype.makeFileGlob = function () {
  var glob = require('glob');
  var outputPath = this.outputPath;
  var globOptions = {nodir: true};

  return glob.sync(outputPath + '/**/*.{css,js}', globOptions);
};

AssetPrinterSize.prototype.validateAssetPath = function (files) {
  if (!files.length) {
    throw new Error('No asset files found in the path provided: ' + this.outputPath);
  }
};

module.exports = AssetPrinterSize;
<|endoftext|>


<commit_before>import webpack from 'webpack'
import { RawSource } from 'webpack-sources'
import { join, relative, dirname } from 'path'
import { IS_BUNDLED_PAGE_REGEX } from 'next-server/constants'

const SSR_MODULE_CACHE_FILENAME = 'ssr-module-cache.js'

// By default webpack keeps initialized modules per-module.
// This means that if you have 2 entrypoints loaded into the same app
// they will *not* share the same instance
// This creates many issues when developers / libraries rely on the singleton pattern
// As this pattern assumes every module will have 1 instance
// This plugin overrides webpack's code generation step to replace `installedModules`
// The replacement is a require for a file that's also generated here that only exports an empty object
// Because of Node.js's single instance modules this makes webpack share all initialized instances
// Do note that this module is only geared towards the `node` compilation target.
// For the client side compilation we use `runtimeChunk: 'single'`
export default class NextJsSsrImportPlugin {
  constructor (options) {
    this.options = options
  }
  apply (compiler) {
    const { outputPath } = this.options
    compiler.hooks.emit.tapAsync('NextJsSSRModuleCache', (compilation, callback) => {
      compilation.assets[SSR_MODULE_CACHE_FILENAME] = new RawSource(`
      /* This cache is used by webpack for instantiated modules */
      module.exports = {}
      `)
      callback()
    })
    compiler.hooks.compilation.tap('NextJsSSRModuleCache', (compilation) => {
      compilation.mainTemplate.hooks.localVars.intercept({
        register (tapInfo) {
          if (tapInfo.name === 'MainTemplate') {
            const originalFn = tapInfo.fn
            tapInfo.fn = (source, chunk) => {
              // If the chunk is not part of the pages directory we have to keep the original behavior,
              // otherwise webpack will error out when the file is used before the compilation finishes
              // this is the case with mini-css-extract-plugin
              if (!IS_BUNDLED_PAGE_REGEX.exec(chunk.name) && chunk.name !== 'error-debug.js') {
                return originalFn(source, chunk)
              }
              const pagePath = join(outputPath, dirname(chunk.name))
              let relativePathToBaseDir = relative(pagePath, join(outputPath, SSR_MODULE_CACHE_FILENAME))
              if (chunk.name === 'error-debug.js') {
                relativePathToBaseDir = `./${relativePathToBaseDir}`
              }

              // Make sure even in windows, the path looks like in unix
              // Node.js require system will convert it accordingly
              const relativePathToBaseDirNormalized = relativePathToBaseDir.replace(/\\/g, '/')
              return webpack.Template.asString([
                source,
                '// The module cache',
                `var installedModules = require('${relativePathToBaseDirNormalized}');`
              ])
            }
          }
          return tapInfo
        }
      })
    })
  }
}
<commit_msg>Remove error-debug.js checks as the chunk is removed (#6761)<commit_after>import webpack from 'webpack'
import { RawSource } from 'webpack-sources'
import { join, relative, dirname } from 'path'
import { IS_BUNDLED_PAGE_REGEX } from 'next-server/constants'

const SSR_MODULE_CACHE_FILENAME = 'ssr-module-cache.js'

// By default webpack keeps initialized modules per-module.
// This means that if you have 2 entrypoints loaded into the same app
// they will *not* share the same instance
// This creates many issues when developers / libraries rely on the singleton pattern
// As this pattern assumes every module will have 1 instance
// This plugin overrides webpack's code generation step to replace `installedModules`
// The replacement is a require for a file that's also generated here that only exports an empty object
// Because of Node.js's single instance modules this makes webpack share all initialized instances
// Do note that this module is only geared towards the `node` compilation target.
// For the client side compilation we use `runtimeChunk: 'single'`
export default class NextJsSsrImportPlugin {
  constructor (options) {
    this.options = options
  }
  apply (compiler) {
    const { outputPath } = this.options
    compiler.hooks.emit.tapAsync('NextJsSSRModuleCache', (compilation, callback) => {
      compilation.assets[SSR_MODULE_CACHE_FILENAME] = new RawSource(`
      /* This cache is used by webpack for instantiated modules */
      module.exports = {}
      `)
      callback()
    })
    compiler.hooks.compilation.tap('NextJsSSRModuleCache', (compilation) => {
      compilation.mainTemplate.hooks.localVars.intercept({
        register (tapInfo) {
          if (tapInfo.name === 'MainTemplate') {
            const originalFn = tapInfo.fn
            tapInfo.fn = (source, chunk) => {
              // If the chunk is not part of the pages directory we have to keep the original behavior,
              // otherwise webpack will error out when the file is used before the compilation finishes
              // this is the case with mini-css-extract-plugin
              if (!IS_BUNDLED_PAGE_REGEX.exec(chunk.name)) {
                return originalFn(source, chunk)
              }
              const pagePath = join(outputPath, dirname(chunk.name))
              let relativePathToBaseDir = relative(pagePath, join(outputPath, SSR_MODULE_CACHE_FILENAME))

              // Make sure even in windows, the path looks like in unix
              // Node.js require system will convert it accordingly
              const relativePathToBaseDirNormalized = relativePathToBaseDir.replace(/\\/g, '/')
              return webpack.Template.asString([
                source,
                '// The module cache',
                `var installedModules = require('${relativePathToBaseDirNormalized}');`
              ])
            }
          }
          return tapInfo
        }
      })
    })
  }
}
<|endoftext|>


<commit_before>/**
 * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
 * @author mrdoob / http://mrdoob.com/
 */

THREE.CSS3DObject = function ( element ) {

	THREE.Object3D.call( this );

	this.element = element;
	this.element.style.position = "absolute";
	this.element.style.WebkitTransformStyle = 'preserve-3d';
	this.element.style.MozTransformStyle = 'preserve-3d';
	this.element.style.oTransformStyle = 'preserve-3d';
	this.element.style.transformStyle = 'preserve-3d';

};

THREE.CSS3DObject.prototype = Object.create( THREE.Object3D.prototype );

THREE.CSS3DSprite = function ( element ) {

	THREE.CSS3DObject.call( this, element );

};

THREE.CSS3DSprite.prototype = Object.create( THREE.CSS3DObject.prototype );

//

THREE.CSS3DRenderer = function () {

	console.log( 'THREE.CSS3DRenderer', THREE.REVISION );

	var _width, _height;
	var _widthHalf, _heightHalf;
	var _projector = new THREE.Projector();

	var _tmpMatrix = new THREE.Matrix4();

	this.domElement = document.createElement( 'div' );

	this.domElement.style.overflow = 'hidden';

	this.domElement.style.WebkitTransformStyle = 'preserve-3d';
	this.domElement.style.WebkitPerspectiveOrigin = '50% 50%';

	this.domElement.style.MozTransformStyle = 'preserve-3d';
	this.domElement.style.MozPerspectiveOrigin = '50% 50%';

	this.domElement.style.oTransformStyle = 'preserve-3d';
	this.domElement.style.oPerspectiveOrigin = '50% 50%';

	this.domElement.style.transformStyle = 'preserve-3d';
	this.domElement.style.perspectiveOrigin = '50% 50%';

	// TODO: Shouldn't it be possible to remove cameraElement?

	this.cameraElement = document.createElement( 'div' );

	this.cameraElement.style.WebkitTransformStyle = 'preserve-3d';
	this.cameraElement.style.MozTransformStyle = 'preserve-3d';
	this.cameraElement.style.oTransformStyle = 'preserve-3d';
	this.cameraElement.style.transformStyle = 'preserve-3d';

	this.domElement.appendChild( this.cameraElement );

	this.setSize = function ( width, height ) {

		_width = width;
		_height = height;

		_widthHalf = _width / 2;
		_heightHalf = _height / 2;

		this.domElement.style.width = width + 'px';
		this.domElement.style.height = height + 'px';

		this.cameraElement.style.width = width + 'px';
		this.cameraElement.style.height = height + 'px';

	};

	var epsilon = function ( value ) {

		return Math.abs( value ) < 0.000001 ? 0 : value;

        };

	var getCameraCSSMatrix = function ( matrix ) {

		var elements = matrix.elements;

		return 'matrix3d(' +
			epsilon( elements[ 0 ] ) + ',' +
			epsilon( - elements[ 1 ] ) + ',' +
			epsilon( elements[ 2 ] ) + ',' +
			epsilon( elements[ 3 ] ) + ',' +
			epsilon( elements[ 4 ] ) + ',' +
			epsilon( - elements[ 5 ] ) + ',' +
			epsilon( elements[ 6 ] ) + ',' +
			epsilon( elements[ 7 ] ) + ',' +
			epsilon( elements[ 8 ] ) + ',' +
			epsilon( - elements[ 9 ] ) + ',' +
			epsilon( elements[ 10 ] ) + ',' +
			epsilon( elements[ 11 ] ) + ',' +
			epsilon( elements[ 12 ] ) + ',' +
			epsilon( - elements[ 13 ] ) + ',' +
			epsilon( elements[ 14 ] ) + ',' +
			epsilon( elements[ 15 ] ) +
		')';

	}

	var getObjectCSSMatrix = function ( matrix ) {

		var elements = matrix.elements;

		return 'translate3d(-50%,-50%,0) matrix3d(' +
			epsilon( elements[ 0 ] ) + ',' +
			epsilon( elements[ 1 ] ) + ',' +
			epsilon( elements[ 2 ] ) + ',' +
			epsilon( elements[ 3 ] ) + ',' +
			epsilon( - elements[ 4 ] ) + ',' +
			epsilon( - elements[ 5 ] ) + ',' +
			epsilon( - elements[ 6 ] ) + ',' +
			epsilon( - elements[ 7 ] ) + ',' +
			epsilon( elements[ 8 ] ) + ',' +
			epsilon( elements[ 9 ] ) + ',' +
			epsilon( elements[ 10 ] ) + ',' +
			epsilon( elements[ 11 ] ) + ',' +
			epsilon( elements[ 12 ] ) + ',' +
			epsilon( elements[ 13 ] ) + ',' +
			epsilon( elements[ 14 ] ) + ',' +
			epsilon( elements[ 15 ] ) +
		')';

	}

	this.render = function ( scene, camera ) {

		var fov = 0.5 / Math.tan( camera.fov * Math.PI / 360 ) * _height;

		this.domElement.style.WebkitPerspective = fov + "px";
		this.domElement.style.MozPerspective = fov + "px";
		this.domElement.style.oPerspective = fov + "px";
		this.domElement.style.perspective = fov + "px";

		var objects = _projector.projectScene( scene, camera, false ).objects;

		var style = "translate3d(0,0," + fov + "px)" + getCameraCSSMatrix( camera.matrixWorldInverse ) + " translate3d(" + _widthHalf + "px," + _heightHalf + "px, 0)";

		this.cameraElement.style.WebkitTransform = style;
		this.cameraElement.style.MozTransform = style;
		this.cameraElement.style.oTransform = style;
		this.cameraElement.style.transform = style;

		for ( var i = 0, il = objects.length; i < il; i ++ ) {

			var object = objects[ i ].object;

			if ( object instanceof THREE.CSS3DObject ) {

				var element = object.element;

				if ( object instanceof THREE.CSS3DSprite ) {

					// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/

					_tmpMatrix.copy( camera.matrixWorldInverse );
					_tmpMatrix.transpose();
					_tmpMatrix.extractPosition( object.matrixWorld );

					_tmpMatrix.elements[ 3 ] = 0;
					_tmpMatrix.elements[ 7 ] = 0;
					_tmpMatrix.elements[ 11 ] = 0;
					_tmpMatrix.elements[ 15 ] = 1;

					style = getObjectCSSMatrix( _tmpMatrix );

				} else {

					style = getObjectCSSMatrix( object.matrixWorld );

				}

				/*
				element.style.WebkitBackfaceVisibility = 'hidden';
				element.style.MozBackfaceVisibility = 'hidden';
				element.style.oBackfaceVisibility = 'hidden';
				element.style.backfaceVisibility = 'hidden';
				*/

				element.style.WebkitTransform = style;
				element.style.MozTransform = style;
				element.style.oTransform = style;
				element.style.transform = style;

				if ( element.parentNode !== this.cameraElement ) {

					this.cameraElement.appendChild( element );

				}

			}

		}

	};

};
<commit_msg>Slightly faster string construction<commit_after>/**
 * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
 * @author mrdoob / http://mrdoob.com/
 */

THREE.CSS3DObject = function ( element ) {

	THREE.Object3D.call( this );

	this.element = element;
	this.element.style.position = "absolute";
	this.element.style.WebkitTransformStyle = 'preserve-3d';
	this.element.style.MozTransformStyle = 'preserve-3d';
	this.element.style.oTransformStyle = 'preserve-3d';
	this.element.style.transformStyle = 'preserve-3d';

};

THREE.CSS3DObject.prototype = Object.create( THREE.Object3D.prototype );

THREE.CSS3DSprite = function ( element ) {

	THREE.CSS3DObject.call( this, element );

};

THREE.CSS3DSprite.prototype = Object.create( THREE.CSS3DObject.prototype );

//

THREE.CSS3DRenderer = function () {

	console.log( 'THREE.CSS3DRenderer', THREE.REVISION );

	var _width, _height;
	var _widthHalf, _heightHalf;
	var _projector = new THREE.Projector();

	var _tmpMatrix = new THREE.Matrix4();

	this.domElement = document.createElement( 'div' );

	this.domElement.style.overflow = 'hidden';

	this.domElement.style.WebkitTransformStyle = 'preserve-3d';
	this.domElement.style.WebkitPerspectiveOrigin = '50% 50%';

	this.domElement.style.MozTransformStyle = 'preserve-3d';
	this.domElement.style.MozPerspectiveOrigin = '50% 50%';

	this.domElement.style.oTransformStyle = 'preserve-3d';
	this.domElement.style.oPerspectiveOrigin = '50% 50%';

	this.domElement.style.transformStyle = 'preserve-3d';
	this.domElement.style.perspectiveOrigin = '50% 50%';

	// TODO: Shouldn't it be possible to remove cameraElement?

	this.cameraElement = document.createElement( 'div' );

	this.cameraElement.style.WebkitTransformStyle = 'preserve-3d';
	this.cameraElement.style.MozTransformStyle = 'preserve-3d';
	this.cameraElement.style.oTransformStyle = 'preserve-3d';
	this.cameraElement.style.transformStyle = 'preserve-3d';

	this.domElement.appendChild( this.cameraElement );

	this.setSize = function ( width, height ) {

		_width = width;
		_height = height;

		_widthHalf = _width / 2;
		_heightHalf = _height / 2;

		this.domElement.style.width = width + 'px';
		this.domElement.style.height = height + 'px';

		this.cameraElement.style.width = width + 'px';
		this.cameraElement.style.height = height + 'px';

	};

	var epsilon = function ( value ) {

		return Math.abs( value ) < 0.000001 ? 0 : value;

        };

	var getCameraCSSMatrix = function ( matrix ) {

		var elements = matrix.elements;

		return ['matrix3d(' ,
			epsilon(elements[0]) , ',' ,
			epsilon(-elements[1]) , ',' ,
			epsilon(elements[2]) , ',' ,
			epsilon(elements[3]) , ',' ,
			epsilon(elements[4]) , ',' ,
			epsilon(-elements[5]) , ',' ,
			epsilon(elements[6]) , ',' ,
			epsilon(elements[7]) , ',' ,
			epsilon(elements[8]) , ',' ,
			epsilon(-elements[9]) , ',' ,
			epsilon(elements[10]) , ',' ,
			epsilon(elements[11]) , ',' ,
			epsilon(elements[12]) , ',' ,
			epsilon(-elements[13]) , ',' ,
			epsilon(elements[14]) , ',' ,
			epsilon(elements[15]) ,
		')'].join('');

	}

	var getObjectCSSMatrix = function ( matrix ) {

		var elements = matrix.elements;

		return ['translate3d(-50%,-50%,0) matrix3d(' ,
			epsilon( elements[ 0 ] ) , ',' ,
			epsilon( elements[ 1 ] ) , ',' ,
			epsilon( elements[ 2 ] ) , ',' ,
			epsilon( elements[ 3 ] ) , ',' ,
			epsilon( - elements[ 4 ] ) , ',' ,
			epsilon( - elements[ 5 ] ) , ',' ,
			epsilon( - elements[ 6 ] ) , ',' ,
			epsilon( - elements[ 7 ] ) , ',' ,
			epsilon( elements[ 8 ] ) , ',' ,
			epsilon( elements[ 9 ] ) , ',' ,
			epsilon( elements[ 10 ] ) , ',' ,
			epsilon( elements[ 11 ] ) , ',' ,
			epsilon( elements[ 12 ] ) , ',' ,
			epsilon( elements[ 13 ] ) , ',' ,
			epsilon( elements[ 14 ] ) , ',' ,
			epsilon( elements[ 15 ] ) ,
		')'].join('');

	}

	this.render = function ( scene, camera ) {

		var fov = 0.5 / Math.tan( camera.fov * Math.PI / 360 ) * _height;

		this.domElement.style.WebkitPerspective = fov + "px";
		this.domElement.style.MozPerspective = fov + "px";
		this.domElement.style.oPerspective = fov + "px";
		this.domElement.style.perspective = fov + "px";

		var objects = _projector.projectScene( scene, camera, false ).objects;

		var style = "translate3d(0,0," + fov + "px)" + getCameraCSSMatrix( camera.matrixWorldInverse ) + " translate3d(" + _widthHalf + "px," + _heightHalf + "px, 0)";

		this.cameraElement.style.WebkitTransform = style;
		this.cameraElement.style.MozTransform = style;
		this.cameraElement.style.oTransform = style;
		this.cameraElement.style.transform = style;

		for ( var i = 0, il = objects.length; i < il; i ++ ) {

			var object = objects[ i ].object;

			if ( object instanceof THREE.CSS3DObject ) {

				var element = object.element;

				if ( object instanceof THREE.CSS3DSprite ) {

					// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/

					_tmpMatrix.copy( camera.matrixWorldInverse );
					_tmpMatrix.transpose();
					_tmpMatrix.extractPosition( object.matrixWorld );

					_tmpMatrix.elements[ 3 ] = 0;
					_tmpMatrix.elements[ 7 ] = 0;
					_tmpMatrix.elements[ 11 ] = 0;
					_tmpMatrix.elements[ 15 ] = 1;

					style = getObjectCSSMatrix( _tmpMatrix );

				} else {

					style = getObjectCSSMatrix( object.matrixWorld );

				}

				/*
				element.style.WebkitBackfaceVisibility = 'hidden';
				element.style.MozBackfaceVisibility = 'hidden';
				element.style.oBackfaceVisibility = 'hidden';
				element.style.backfaceVisibility = 'hidden';
				*/

				element.style.WebkitTransform = style;
				element.style.MozTransform = style;
				element.style.oTransform = style;
				element.style.transform = style;

				if ( element.parentNode !== this.cameraElement ) {

					this.cameraElement.appendChild( element );

				}

			}

		}

	};

};
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useRef, useEffect, useState } from 'react';
import classnames from 'classnames';

/**
 * Internal dependencies
 */
import { useStory, useDropTargets } from '../../app';
import Movable from '../movable';
import objectWithout from '../../utils/objectWithout';
import { useTransform } from '../transform';
import { useUnits } from '../../units';
import { getDefinitionForType } from '../../elements';
import { useGlobalIsKeyPressed } from '../keyboard';
import useBatchingCallback from '../../utils/useBatchingCallback';
import isTargetOutOfContainer from '../../utils/isTargetOutOfContainer';
import useCanvas from './useCanvas';

const EMPTY_HANDLES = [];
const VERTICAL_HANDLES = ['n', 's'];
const HORIZONTAL_HANDLES = ['e', 'w'];
const DIAGONAL_HANDLES = ['nw', 'ne', 'sw', 'se'];

function SingleSelectionMovable({
  selectedElement,
  targetEl,
  pushEvent,
  isEditMode,
  editMoveableRef,
}) {
  let moveable = useRef(null);
  if (isEditMode) {
    moveable = editMoveableRef;
  }
  const [isDragging, setIsDragging] = useState(false);
  const [isResizingFromCorner, setIsResizingFromCorner] = useState(true);

  const { updateSelectedElements, deleteSelectedElements } = useStory(
    (state) => ({
      updateSelectedElements: state.actions.updateSelectedElements,
      deleteSelectedElements: state.actions.deleteSelectedElements,
    })
  );
  const {
    canvasWidth,
    canvasHeight,
    nodesById,
    fullbleedContainer,
  } = useCanvas(
    ({
      state: {
        pageSize: { width: canvasWidth, height: canvasHeight },
        nodesById,
        fullbleedContainer,
      },
    }) => ({ canvasWidth, canvasHeight, nodesById, fullbleedContainer })
  );
  const {
    getBox,
    editorToDataX,
    editorToDataY,
    dataToEditorY,
    dataToEditorX,
  } = useUnits(
    ({
      actions: {
        getBox,
        editorToDataX,
        editorToDataY,
        dataToEditorY,
        dataToEditorX,
      },
    }) => ({
      getBox,
      editorToDataX,
      editorToDataY,
      dataToEditorY,
      dataToEditorX,
    })
  );
  const {
    actions: { pushTransform },
  } = useTransform();
  const {
    state: { activeDropTargetId, draggingResource },
    actions: { handleDrag, handleDrop, setDraggingResource, isDropSource },
  } = useDropTargets();

  const otherNodes = Object.values(
    objectWithout(nodesById, [selectedElement.id])
  );

  const actionsEnabled = !selectedElement.isBackground;

  const latestEvent = useRef();

  useEffect(() => {
    latestEvent.current = pushEvent;
  }, [pushEvent]);

  useEffect(() => {
    if (!moveable.current) {
      return;
    }
    // If we have persistent event then let's use that, ensuring the targets match.
    if (
      latestEvent.current &&
      targetEl.contains(latestEvent.current.target) &&
      actionsEnabled
    ) {
      moveable.current.moveable.dragStart(latestEvent.current);
    }
    moveable.current.updateRect();
  }, [targetEl, moveable, actionsEnabled]);

  // Update moveable with whatever properties could be updated outside moveable
  // itself.
  useEffect(() => {
    if (!moveable.current) {
      return;
    }
    moveable.current.updateRect();
  });

  // ⌘ key disables snapping
  const snapDisabled = useGlobalIsKeyPressed('meta');

  // ⇧ key rotates the element 30 degrees at a time
  const throttleRotation = useGlobalIsKeyPressed('shift');

  const box = getBox(selectedElement);
  const frame = {
    translate: [0, 0],
    rotate: box.rotationAngle,
    resize: [0, 0],
    updates: null,
  };

  const setTransformStyle = (target) => {
    target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px) rotate(${frame.rotate}deg)`;
    if (frame.resize[0]) {
      target.style.width = `${frame.resize[0]}px`;
    }
    if (frame.resize[1]) {
      target.style.height = `${frame.resize[1]}px`;
    }
    pushTransform(selectedElement.id, frame);
  };

  /**
   * Resets Movable once the action is done, sets the initial values.
   *
   * @param {Object} target Target element.
   */
  const resetMoveable = useBatchingCallback(
    (target) => {
      frame.direction = [0, 0];
      frame.translate = [0, 0];
      frame.resize = [0, 0];
      frame.updates = null;
      pushTransform(selectedElement.id, null);
      // Inline start resetting has to be done very carefully here to avoid
      // conflicts with stylesheets. See #3951.
      target.style.transform = '';
      target.style.width = '';
      target.style.height = '';
      setIsResizingFromCorner(true);
      if (moveable.current) {
        moveable.current.updateRect();
      }
    },
    [frame, pushTransform, setIsResizingFromCorner, selectedElement.id]
  );

  const resetDragging = useBatchingCallback(
    (target) => {
      setIsDragging(false);
      setDraggingResource(null);
      resetMoveable(target);
    },
    [setIsDragging, setDraggingResource, resetMoveable]
  );

  const { resizeRules = {}, updateForResizeEvent } = getDefinitionForType(
    selectedElement.type
  );

  const canSnap =
    !snapDisabled &&
    !isEditMode &&
    (!isDragging || (isDragging && !activeDropTargetId));
  const hideHandles = isDragging || Boolean(draggingResource);

  // Removes element if it's outside of canvas.
  const handleElementOutOfCanvas = (target) => {
    if (isTargetOutOfContainer(target, fullbleedContainer)) {
      setIsDragging(false);
      setDraggingResource(null);
      deleteSelectedElements();
      return true;
    }
    return false;
  };

  const minWidth = dataToEditorX(resizeRules.minWidth);
  const minHeight = dataToEditorY(resizeRules.minHeight);
  const aspectRatio = selectedElement.width / selectedElement.height;

  const visuallyHideHandles =
    selectedElement.width <= resizeRules.minWidth ||
    selectedElement.height <= resizeRules.minHeight;

  const classNames = classnames('default-movable', {
    'hide-handles': hideHandles,
    'visually-hide-handles': visuallyHideHandles,
    'type-text': selectedElement.type === 'text',
  });

  return (
    <Movable
      className={classNames}
      zIndex={0}
      ref={moveable}
      target={targetEl}
      edge={true}
      draggable={actionsEnabled}
      resizable={actionsEnabled && !hideHandles}
      rotatable={actionsEnabled && !hideHandles}
      onDrag={({ target, beforeTranslate, clientX, clientY }) => {
        if (isEditMode) {
          return false;
        }
        setIsDragging(true);
        if (isDropSource(selectedElement.type)) {
          setDraggingResource(selectedElement.resource);
        }
        frame.translate = beforeTranslate;
        setTransformStyle(target);
        if (isDropSource(selectedElement.type)) {
          handleDrag(
            selectedElement.resource,
            clientX,
            clientY,
            selectedElement.id
          );
        }
        return undefined;
      }}
      throttleDrag={0}
      onDragStart={({ set }) => {
        if (isEditMode) {
          return false;
        }
        set(frame.translate);
        return undefined;
      }}
      onDragEnd={({ target }) => {
        if (isEditMode) {
          return false;
        }
        if (handleElementOutOfCanvas(target)) {
          return undefined;
        }
        // When dragging finishes, set the new properties based on the original + what moved meanwhile.
        const [deltaX, deltaY] = frame.translate;
        if (
          deltaX !== 0 ||
          deltaY !== 0 ||
          isDropSource(selectedElement.type)
        ) {
          const properties = {
            x: selectedElement.x + editorToDataX(deltaX),
            y: selectedElement.y + editorToDataY(deltaY),
          };
          updateSelectedElements({ properties });
          if (isDropSource(selectedElement.type)) {
            handleDrop(selectedElement.resource, selectedElement.id);
          }
        }
        resetDragging(target);
        return undefined;
      }}
      onResizeStart={({ setOrigin, dragStart, direction }) => {
        setOrigin(['%', '%']);
        if (dragStart) {
          dragStart.set(frame.translate);
        }
        // Lock ratio for diagonal directions (nw, ne, sw, se). Both
        // `direction[]` values for diagonals are either 1 or -1. Non-diagonal
        // directions have 0s.
        const newResizingMode = direction[0] !== 0 && direction[1] !== 0;
        if (isResizingFromCorner !== newResizingMode) {
          setIsResizingFromCorner(newResizingMode);
        }
        if (isEditMode) {
          // In edit mode, we need to signal right away that the action started.
          pushTransform(selectedElement.id, frame);
        }
      }}
      onResize={({ target, direction, width, height, drag }) => {
        let newWidth = width;
        let newHeight = height;
        let updates = null;

        if (isResizingFromCorner) {
          if (newWidth < minWidth) {
            newWidth = minWidth;
            newHeight = newWidth / aspectRatio;
          }
          if (newHeight < minHeight) {
            newHeight = minHeight;
            newWidth = minHeight * aspectRatio;
          }
        } else {
          newHeight = Math.max(newHeight, minHeight);
          newWidth = Math.max(newWidth, minWidth);
        }

        if (updateForResizeEvent) {
          updates = updateForResizeEvent(
            selectedElement,
            direction,
            editorToDataX(newWidth),
            editorToDataY(newHeight)
          );
        }
        if (updates && updates.height) {
          newHeight = dataToEditorY(updates.height);
        }

        target.style.width = `${newWidth}px`;
        target.style.height = `${newHeight}px`;
        frame.direction = direction;
        frame.resize = [newWidth, newHeight];
        frame.translate = drag.beforeTranslate;
        frame.updates = updates;
        setTransformStyle(target);
      }}
      onResizeEnd={({ target }) => {
        if (handleElementOutOfCanvas(target)) {
          return;
        }
        const [editorWidth, editorHeight] = frame.resize;
        if (editorWidth !== 0 && editorHeight !== 0) {
          const { direction } = frame;
          const [deltaX, deltaY] = frame.translate;
          const newWidth = editorToDataX(editorWidth);
          const newHeight = editorToDataY(editorHeight);
          const properties = {
            width: newWidth,
            height: newHeight,
            x: selectedElement.x + editorToDataX(deltaX),
            y: selectedElement.y + editorToDataY(deltaY),
          };
          if (updateForResizeEvent) {
            Object.assign(
              properties,
              updateForResizeEvent(
                selectedElement,
                direction,
                newWidth,
                newHeight
              )
            );
          }
          updateSelectedElements({ properties });
        }
        resetMoveable(target);
      }}
      onRotateStart={({ set }) => {
        if (isEditMode) {
          // In edit mode, we need to signal right away that the action started.
          pushTransform(selectedElement.id, frame);
        }
        set(frame.rotate);
      }}
      onRotate={({ target, beforeRotate }) => {
        frame.rotate = ((beforeRotate % 360) + 360) % 360;
        setTransformStyle(target);
      }}
      onRotateEnd={({ target }) => {
        if (handleElementOutOfCanvas(target)) {
          return;
        }
        const properties = { rotationAngle: Math.round(frame.rotate) };
        updateSelectedElements({ properties });
        resetMoveable(target);
      }}
      throttleRotate={throttleRotation ? 30 : 0}
      origin={false}
      pinchable={true}
      keepRatio={isResizingFromCorner}
      renderDirections={getRenderDirections(resizeRules)}
      snappable={canSnap}
      snapCenter={canSnap}
      horizontalGuidelines={
        canSnap && actionsEnabled ? [0, canvasHeight / 2, canvasHeight] : []
      }
      verticalGuidelines={
        canSnap && actionsEnabled ? [0, canvasWidth / 2, canvasWidth] : []
      }
      elementGuidelines={canSnap && actionsEnabled ? otherNodes : []}
      snapGap={canSnap}
      isDisplaySnapDigit={false}
    />
  );
}

function getRenderDirections({ vertical, horizontal, diagonal }) {
  return [
    ...(vertical ? VERTICAL_HANDLES : EMPTY_HANDLES),
    ...(horizontal ? HORIZONTAL_HANDLES : EMPTY_HANDLES),
    ...(diagonal ? DIAGONAL_HANDLES : EMPTY_HANDLES),
  ];
}

SingleSelectionMovable.propTypes = {
  selectedElement: PropTypes.object.isRequired,
  targetEl: PropTypes.object.isRequired,
  pushEvent: PropTypes.object,
  isEditMode: PropTypes.bool,
  editMoveableRef: PropTypes.object,
};

export default SingleSelectionMovable;
<commit_msg>Use combined refs.<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useRef, useEffect, useState } from 'react';
import classnames from 'classnames';

/**
 * Internal dependencies
 */
import { useStory, useDropTargets } from '../../app';
import Movable from '../movable';
import objectWithout from '../../utils/objectWithout';
import { useTransform } from '../transform';
import { useUnits } from '../../units';
import { getDefinitionForType } from '../../elements';
import { useGlobalIsKeyPressed } from '../keyboard';
import useBatchingCallback from '../../utils/useBatchingCallback';
import isTargetOutOfContainer from '../../utils/isTargetOutOfContainer';
import useCombinedRefs from '../../utils/useCombinedRefs';
import useCanvas from './useCanvas';

const EMPTY_HANDLES = [];
const VERTICAL_HANDLES = ['n', 's'];
const HORIZONTAL_HANDLES = ['e', 'w'];
const DIAGONAL_HANDLES = ['nw', 'ne', 'sw', 'se'];

function SingleSelectionMovable({
  selectedElement,
  targetEl,
  pushEvent,
  isEditMode,
  editMoveableRef,
}) {
  const moveable = useRef(null);
  const [isDragging, setIsDragging] = useState(false);
  const [isResizingFromCorner, setIsResizingFromCorner] = useState(true);

  const { updateSelectedElements, deleteSelectedElements } = useStory(
    (state) => ({
      updateSelectedElements: state.actions.updateSelectedElements,
      deleteSelectedElements: state.actions.deleteSelectedElements,
    })
  );
  const {
    canvasWidth,
    canvasHeight,
    nodesById,
    fullbleedContainer,
  } = useCanvas(
    ({
      state: {
        pageSize: { width: canvasWidth, height: canvasHeight },
        nodesById,
        fullbleedContainer,
      },
    }) => ({ canvasWidth, canvasHeight, nodesById, fullbleedContainer })
  );
  const {
    getBox,
    editorToDataX,
    editorToDataY,
    dataToEditorY,
    dataToEditorX,
  } = useUnits(
    ({
      actions: {
        getBox,
        editorToDataX,
        editorToDataY,
        dataToEditorY,
        dataToEditorX,
      },
    }) => ({
      getBox,
      editorToDataX,
      editorToDataY,
      dataToEditorY,
      dataToEditorX,
    })
  );
  const {
    actions: { pushTransform },
  } = useTransform();
  const {
    state: { activeDropTargetId, draggingResource },
    actions: { handleDrag, handleDrop, setDraggingResource, isDropSource },
  } = useDropTargets();

  const otherNodes = Object.values(
    objectWithout(nodesById, [selectedElement.id])
  );

  const actionsEnabled = !selectedElement.isBackground;

  const latestEvent = useRef();

  useEffect(() => {
    latestEvent.current = pushEvent;
  }, [pushEvent]);

  useEffect(() => {
    if (!moveable.current) {
      return;
    }
    // If we have persistent event then let's use that, ensuring the targets match.
    if (
      latestEvent.current &&
      targetEl.contains(latestEvent.current.target) &&
      actionsEnabled
    ) {
      moveable.current.moveable.dragStart(latestEvent.current);
    }
    moveable.current.updateRect();
  }, [targetEl, moveable, actionsEnabled]);

  // Update moveable with whatever properties could be updated outside moveable
  // itself.
  useEffect(() => {
    if (!moveable.current) {
      return;
    }
    moveable.current.updateRect();
  });

  // ⌘ key disables snapping
  const snapDisabled = useGlobalIsKeyPressed('meta');

  // ⇧ key rotates the element 30 degrees at a time
  const throttleRotation = useGlobalIsKeyPressed('shift');

  const box = getBox(selectedElement);
  const frame = {
    translate: [0, 0],
    rotate: box.rotationAngle,
    resize: [0, 0],
    updates: null,
  };

  const setTransformStyle = (target) => {
    target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px) rotate(${frame.rotate}deg)`;
    if (frame.resize[0]) {
      target.style.width = `${frame.resize[0]}px`;
    }
    if (frame.resize[1]) {
      target.style.height = `${frame.resize[1]}px`;
    }
    pushTransform(selectedElement.id, frame);
  };

  /**
   * Resets Movable once the action is done, sets the initial values.
   *
   * @param {Object} target Target element.
   */
  const resetMoveable = useBatchingCallback(
    (target) => {
      frame.direction = [0, 0];
      frame.translate = [0, 0];
      frame.resize = [0, 0];
      frame.updates = null;
      pushTransform(selectedElement.id, null);
      // Inline start resetting has to be done very carefully here to avoid
      // conflicts with stylesheets. See #3951.
      target.style.transform = '';
      target.style.width = '';
      target.style.height = '';
      setIsResizingFromCorner(true);
      if (moveable.current) {
        moveable.current.updateRect();
      }
    },
    [frame, pushTransform, setIsResizingFromCorner, selectedElement.id]
  );

  const resetDragging = useBatchingCallback(
    (target) => {
      setIsDragging(false);
      setDraggingResource(null);
      resetMoveable(target);
    },
    [setIsDragging, setDraggingResource, resetMoveable]
  );

  const { resizeRules = {}, updateForResizeEvent } = getDefinitionForType(
    selectedElement.type
  );

  const canSnap =
    !snapDisabled &&
    !isEditMode &&
    (!isDragging || (isDragging && !activeDropTargetId));
  const hideHandles = isDragging || Boolean(draggingResource);

  // Removes element if it's outside of canvas.
  const handleElementOutOfCanvas = (target) => {
    if (isTargetOutOfContainer(target, fullbleedContainer)) {
      setIsDragging(false);
      setDraggingResource(null);
      deleteSelectedElements();
      return true;
    }
    return false;
  };

  const minWidth = dataToEditorX(resizeRules.minWidth);
  const minHeight = dataToEditorY(resizeRules.minHeight);
  const aspectRatio = selectedElement.width / selectedElement.height;

  const visuallyHideHandles =
    selectedElement.width <= resizeRules.minWidth ||
    selectedElement.height <= resizeRules.minHeight;

  const classNames = classnames('default-movable', {
    'hide-handles': hideHandles,
    'visually-hide-handles': visuallyHideHandles,
    'type-text': selectedElement.type === 'text',
  });

  return (
    <Movable
      className={classNames}
      zIndex={0}
      ref={useCombinedRefs(moveable, editMoveableRef)}
      target={targetEl}
      edge={true}
      draggable={actionsEnabled}
      resizable={actionsEnabled && !hideHandles}
      rotatable={actionsEnabled && !hideHandles}
      onDrag={({ target, beforeTranslate, clientX, clientY }) => {
        if (isEditMode) {
          return false;
        }
        setIsDragging(true);
        if (isDropSource(selectedElement.type)) {
          setDraggingResource(selectedElement.resource);
        }
        frame.translate = beforeTranslate;
        setTransformStyle(target);
        if (isDropSource(selectedElement.type)) {
          handleDrag(
            selectedElement.resource,
            clientX,
            clientY,
            selectedElement.id
          );
        }
        return undefined;
      }}
      throttleDrag={0}
      onDragStart={({ set }) => {
        if (isEditMode) {
          return false;
        }
        set(frame.translate);
        return undefined;
      }}
      onDragEnd={({ target }) => {
        if (isEditMode) {
          return false;
        }
        if (handleElementOutOfCanvas(target)) {
          return undefined;
        }
        // When dragging finishes, set the new properties based on the original + what moved meanwhile.
        const [deltaX, deltaY] = frame.translate;
        if (
          deltaX !== 0 ||
          deltaY !== 0 ||
          isDropSource(selectedElement.type)
        ) {
          const properties = {
            x: selectedElement.x + editorToDataX(deltaX),
            y: selectedElement.y + editorToDataY(deltaY),
          };
          updateSelectedElements({ properties });
          if (isDropSource(selectedElement.type)) {
            handleDrop(selectedElement.resource, selectedElement.id);
          }
        }
        resetDragging(target);
        return undefined;
      }}
      onResizeStart={({ setOrigin, dragStart, direction }) => {
        setOrigin(['%', '%']);
        if (dragStart) {
          dragStart.set(frame.translate);
        }
        // Lock ratio for diagonal directions (nw, ne, sw, se). Both
        // `direction[]` values for diagonals are either 1 or -1. Non-diagonal
        // directions have 0s.
        const newResizingMode = direction[0] !== 0 && direction[1] !== 0;
        if (isResizingFromCorner !== newResizingMode) {
          setIsResizingFromCorner(newResizingMode);
        }
        if (isEditMode) {
          // In edit mode, we need to signal right away that the action started.
          pushTransform(selectedElement.id, frame);
        }
      }}
      onResize={({ target, direction, width, height, drag }) => {
        let newWidth = width;
        let newHeight = height;
        let updates = null;

        if (isResizingFromCorner) {
          if (newWidth < minWidth) {
            newWidth = minWidth;
            newHeight = newWidth / aspectRatio;
          }
          if (newHeight < minHeight) {
            newHeight = minHeight;
            newWidth = minHeight * aspectRatio;
          }
        } else {
          newHeight = Math.max(newHeight, minHeight);
          newWidth = Math.max(newWidth, minWidth);
        }

        if (updateForResizeEvent) {
          updates = updateForResizeEvent(
            selectedElement,
            direction,
            editorToDataX(newWidth),
            editorToDataY(newHeight)
          );
        }
        if (updates && updates.height) {
          newHeight = dataToEditorY(updates.height);
        }

        target.style.width = `${newWidth}px`;
        target.style.height = `${newHeight}px`;
        frame.direction = direction;
        frame.resize = [newWidth, newHeight];
        frame.translate = drag.beforeTranslate;
        frame.updates = updates;
        setTransformStyle(target);
      }}
      onResizeEnd={({ target }) => {
        if (handleElementOutOfCanvas(target)) {
          return;
        }
        const [editorWidth, editorHeight] = frame.resize;
        if (editorWidth !== 0 && editorHeight !== 0) {
          const { direction } = frame;
          const [deltaX, deltaY] = frame.translate;
          const newWidth = editorToDataX(editorWidth);
          const newHeight = editorToDataY(editorHeight);
          const properties = {
            width: newWidth,
            height: newHeight,
            x: selectedElement.x + editorToDataX(deltaX),
            y: selectedElement.y + editorToDataY(deltaY),
          };
          if (updateForResizeEvent) {
            Object.assign(
              properties,
              updateForResizeEvent(
                selectedElement,
                direction,
                newWidth,
                newHeight
              )
            );
          }
          updateSelectedElements({ properties });
        }
        resetMoveable(target);
      }}
      onRotateStart={({ set }) => {
        if (isEditMode) {
          // In edit mode, we need to signal right away that the action started.
          pushTransform(selectedElement.id, frame);
        }
        set(frame.rotate);
      }}
      onRotate={({ target, beforeRotate }) => {
        frame.rotate = ((beforeRotate % 360) + 360) % 360;
        setTransformStyle(target);
      }}
      onRotateEnd={({ target }) => {
        if (handleElementOutOfCanvas(target)) {
          return;
        }
        const properties = { rotationAngle: Math.round(frame.rotate) };
        updateSelectedElements({ properties });
        resetMoveable(target);
      }}
      throttleRotate={throttleRotation ? 30 : 0}
      origin={false}
      pinchable={true}
      keepRatio={isResizingFromCorner}
      renderDirections={getRenderDirections(resizeRules)}
      snappable={canSnap}
      snapCenter={canSnap}
      horizontalGuidelines={
        canSnap && actionsEnabled ? [0, canvasHeight / 2, canvasHeight] : []
      }
      verticalGuidelines={
        canSnap && actionsEnabled ? [0, canvasWidth / 2, canvasWidth] : []
      }
      elementGuidelines={canSnap && actionsEnabled ? otherNodes : []}
      snapGap={canSnap}
      isDisplaySnapDigit={false}
    />
  );
}

function getRenderDirections({ vertical, horizontal, diagonal }) {
  return [
    ...(vertical ? VERTICAL_HANDLES : EMPTY_HANDLES),
    ...(horizontal ? HORIZONTAL_HANDLES : EMPTY_HANDLES),
    ...(diagonal ? DIAGONAL_HANDLES : EMPTY_HANDLES),
  ];
}

SingleSelectionMovable.propTypes = {
  selectedElement: PropTypes.object.isRequired,
  targetEl: PropTypes.object.isRequired,
  pushEvent: PropTypes.object,
  isEditMode: PropTypes.bool,
  editMoveableRef: PropTypes.object,
};

export default SingleSelectionMovable;
<|endoftext|>