<commit_before>/**
 * @fileoverview Bind and watch any changes of object
 * @author Maxim Shaydo aka Max Graey <maxgraey@gmail.com>
 * @version 0.0.10
 */

(function (global) {
    'use strict';

    /**
     * Enum for data binding type.
     * @namespace Binding
     */
    var Binding = {};

    /**
     * One way binding
     * @constant
     */
    Binding.OneWay = 0;
    /**
     * Two way binding
     * @constant
     */
    //Binding.TwoWay = 1;
    /**
     * One time binding
     * @constant
     */
    //Binding.Once   = 2;

    /**************/
    /* poly-fills */
    /**************/

    /** @private */
    var filter = Array.prototype.filter.call.bind(Array.prototype.filter);

    /** @private */
    var MutationObserver = global.MutationObserver ||
                           global.WebKitMutationObserver ||
                           global.MozMutationObserver ||
                           global.OMutationObserver ||
                           global.MsMutationObserver;

    /** @private */
    var isArray = Array.isArray || function isArray(arr) {
        return arr !== undefined && arr.constructor === Array;
    };

    var setImmediate = global.setImmediate || (function () {
        var queue = [];
        var addEventListener = global.addEventListener || global.attachEvent;
        var canPost = typeof window !== 'undefined' && global.postMessage && addEventListener;

        if (canPost) {
            var message = global.addEventListener ? 'message' : 'onmessage';
            addEventListener(message, function (event) {
                if (event.source === window && event.data === 'immediate-message-id') {
                    event.stopPropagation();
                    if (queue.length > 0) {
                        var callback = queue.shift();
                        callback();
                    }
                }
            }, true);
        }

        return function (callback) {
            if (canPost) {
                queue.push(callback);
                global.postMessage('immediate-message-id', '*');
            } else {
                setTimeout(callback, 0);
            }
        };
    })();

    /**
     * WeakMap polyfill based on Andrea Giammarchi's solution
     * @private
     */
    var WeakMap = global.WeakMap || function () {
        var id = '_id:' + Math.random(), i = 0;

        function WeakMap() {
            Object.defineProperty(this, id, { value: id + i++ });
        }

        Object.defineProperties(WeakMap.prototype, {
            'has': { value: function (key) { return !!key[this[id]]; }},
            'get': { value: function (key) { return   key[this[id]]; }},
            'set': {
                value: function (key, value) {
                    Object.defineProperty(key, this[id], {
                        configurable: true,
                        value: value
                    });
                }
            },
            'delete': {
                value: function (key) {
                    var r = this.has(key);
                    if (r) { delete key[this[id]]; }
                    return r;
                }
            }
        });

        return WeakMap;
    };

    /****************************/
    /* private utillity methods */
    /****************************/

    /** @private */
    var observers_  = new WeakMap();
    var spacesExpr_ = /\s+/;
    var defaultObserveOptions_ = {
        subtree: true,
        childList: true,
        characterData: true,
        characterDataOldValue: true,
        attributes: true,
        attributeOldValue: true,
        attributeFilter: undefined
    };

    /** @private */
    function asyncWrap(callback, skipImmutables) {
        // for node.js
        if (typeof module !== 'undefined' && module.exports && process) {
            return function (target, property, newValue, oldValue) {
                if (skipImmutables && (newValue == oldValue || newValue === !!oldValue)) {
                    //console.log('skipeed');
                    return;
                }

                process.nextTick(function () {
                    callback(target, property, newValue, oldValue);
                });
            };
        } else {
            return function (target, property, newValue, oldValue) {
                if (skipImmutables && (newValue == oldValue || newValue === !!oldValue)) {
                    //console.log('skipeed');
                    return;
                }

                setImmediate(function () {
                    callback(target, property, newValue, oldValue);
                });
            };
        }
    }

    /** @private */
    function hasPropertyAccessors(object, property) { // has get or set
        var descriptor = Object.getOwnPropertyDescriptor(object, property);
        return !!descriptor && (!!descriptor.get || !!descriptor.set);
    }

     /** @private */
    function isPropertyConfigurable(propertyDescriptor) { // has get or set
        return !!propertyDescriptor && propertyDescriptor.configurable;
    }

    /** @private */
    function isHtmlElement(obj) {
        return (
            typeof HTMLElement === 'object' ?
                 obj instanceof HTMLElement :
                 typeof obj === 'object' && obj &&
                    obj.nodeType === 1 && typeof obj.nodeName === 'string'
        );
    }

    /** @private */
    function emptyProperties(properties) {
        return (typeof properties === 'undefined' || !properties ||
            (isArray(properties) && properties.length === 0));
    }

    /** @private */
    function descriptorForProperty(object, property, callback) {
        var descriptor = {
            oldValue: object[property],
            get: function () {
                return descriptor.oldValue;
            },
            set: function (value) {
                callback(object, property, value, descriptor.oldValue);
                descriptor.oldValue = value;
            },
            configurable: true,
            enumerable: true
        };

        return descriptor;
    }

    /** @private */
    function descriptorForAccessingProperty(object, property, callback) {
        var originalDesc = Object.getOwnPropertyDescriptor(object, property);
        var setter = function () {};
        var getter = function () {};

        if (!isPropertyConfigurable(originalDesc)) {
            throw new TypeError('The propery ' + property + ' is not configurable');
        }

        if (originalDesc) {
            if (typeof originalDesc.set === 'function') {
                setter = originalDesc.set;
            }

            if (typeof originalDesc.get === 'function') {
                getter = originalDesc.get;
            }
        }

        var descriptor = {
            oldValue: getter.call(object),
            get: getter,
            set: function (value) {
                var result = setter.call(object, value);
                callback(object, property, value, descriptor.oldValue);
                descriptor.oldValue = value;
                return result;
            },
            configurable: true,
            enumerable: true
        };

        return descriptor;
    }

    /** @private */
    function bindProperty(object, property, callback) {
        if (!(property in object)) {
            return;
        }

        var descriptor;
        if (hasPropertyAccessors(object, property)) {
            descriptor = descriptorForAccessingProperty(object, property, callback);
        } else {
            descriptor = descriptorForProperty(object, property, callback);
        }

        Object.defineProperty(object, property, descriptor);
    }

    /** @private */
    function unbindProperty(object, property) {
        if (object.hasOwnProperty(property)) {
            var value = object[property];
            delete object[property];
            object[property] = value;
        }
    }

    /** @private */
    function bindProperties(object, properties, callback) {
        if (emptyProperties(properties)) {
            properties = Object.keys(object);
        }

        for (var i = 0, len = properties.length; i < len; ++i) {
            bindProperty(object, properties[i], callback);
        }
    }

    /** @private */
    function unbindProperties(object, properties) {
        if (emptyProperties(properties)) {
            properties = Object.keys(object);
        }

        for (var i = 0, len = properties.length; i < len; ++i) {
            unbindProperty(object, properties[i]);
        }
    }

    /** @private */
    function validateArguments(object, callback) {
        if (typeof callback === 'undefined') {
            throw new ReferenceError('callback is not defined');
        }

        if (typeof object === 'undefined' || !object) {
           return false;
        }

        return true;
    }

    /** @private */
    function bindHtmlElementProperties(object, properties, callback, options) {
        if (!MutationObserver) {
            throw new Error('MutationObserver is not supported in your browser!');
        }

        var skipImmutables = options.skipImmutables;
        var handleEvents   = options.handleEvents;

        if (typeof handleEvents === 'undefined') {
            handleEvents = true;
        }

        if (typeof properties === 'string') {
            var expr = /\s+/;
            if (expr.test(properties)) {
                properties = properties.split(expr);
            } else {
                properties = [properties];
            }
        }

        if (isArray(properties) && properties.length === 0) {
            properties = undefined;
        }

        // add event changes
        if (handleEvents) {
            object.addEventListener('click', function (event) {
                callback(object, event.type, event, null);
            }, false);

            switch (object.nodeName) {
                case 'INPUT':
                case 'SELECT':
                    object.addEventListener('change', function (event) {
                        callback(object, event.type, event, null);
                    }, false);
                break;
                case 'TEXTAREA':
                    object.addEventListener('input', function (event) {
                        callback(object, event.type, event, null);
                    }, false);
                break;
            }
        }

        var observer = new MutationObserver(function (mutations) {
            var i, len;
            for (i = 0, len = mutations.length; i < len; ++i) {
                var mutation = mutations[i];
                //console.log(mutation);

                var property = mutation.type;
                var newValue = mutation.target.nodeValue;

                if (mutation.attributeName) {
                    property = mutation.attributeName;
                    newValue = object[property];

                    if (newValue && newValue.length) {
                        var value = {};
                        for (i = 0, len = value.length; i < len; ++i) {
                            var attribure    = newValue[i];
                            value[attribure] = newValue[attribure];
                        }
                        newValue = value;
                    }
                }

                if (skipImmutables && (newValue == mutation.oldValue || newValue === !!mutation.oldValue)) {
                    //console.log('skipeed');
                    return ;
                }

                callback(object, property, newValue, mutation.oldValue);
            }
        });

        var mutableOptions = Object.create(defaultObserveOptions_);

        mutableOptions.attributeFilter = properties;
        observer.observe(object, mutableOptions);
        observers_.set(object, { observer: observer, options: mutableOptions });
    }

    /** @private */
    function unbindHtmlElementProperties(object, properties) {
        var observerAndOptions = observers_.get(object);
        var observer = observerAndOptions.observer;
        var options  = observerAndOptions.options;

        observer.disconnect();
        if (emptyProperties(properties)) {
            observers_.delete(object);
        } else {
            if (typeof properties === 'string') {
                if (spacesExpr_.test(properties)) {
                    properties = properties.split(spacesExpr_);
                } else {
                    properties = [properties];
                }
            }

            var len = properties.length;
            var attributeFilter = options.attributeFilter;

            if (!attributeFilter) {
                attributeFilter = [];
                for (var attributeName in object) {
                    attributeFilter.push(attributeName);
                }
            }

            options.attributeFilter = filter(attributeFilter, function (property) {
                for (var i = 0; i < len; ++i) {
                    return (property !== properties[i]);
                }
            });

            observer.observe(object, options);
        }
    }

    /******************/
    /* Public methods */
    /******************/

    /**
     * The Binding.on definition.
     * @memberof Binding
     * @param {object} object Object target.
     * @param {?string|string[]} [properties] The name of property or array of names of property on which you wish to monitor changes.
     * @param {function} callback A function to call when the specified property's value changes.
     * @param {object}  [options] - options.
     * @param {number}  [options.bindingType] - Binding enum.
     * @param {boolean} [options.deep=false] - recursive watch all sub-object properties.
     * @param {boolean} [options.async=false] - asynchronous callback mode.
     * @param {boolean} [options.skipImmutables=true] - skip immutables changes in async mode.
     * @param {boolean} [options.handleEvents=true] - listen some DOMElement events (experimental)
     *
     * @return {boolean} True if callback is exectly asynchronous otherwise False.
     * @throws {Error|ReferenceError}
     */
    Binding.on = function (object, properties, callback, options) {
        if (!validateArguments(object, callback)) {
            return false;
        }

        if (typeof options !== 'object') {
            options = {};
        }

        var skipImmutables = options.skipImmutables;
        if (typeof skipImmutables === 'undefined') {
            skipImmutables = true;
        }

        var allProps    = (typeof properties === 'undefined' || !properties || !properties.length);
        var deep        = !!options.deep && allProps;
        var async       = !!options.async;
        var bindingType = options.bindingType || Binding.OneWay;

        if (async) {
            callback = asyncWrap(callback, skipImmutables);
        }

        if (isHtmlElement(object)) {
            bindHtmlElementProperties(object, properties, callback, options);
            return true;
        }

        // fast javascript object observing
        if (typeof properties === 'string') {
            if (spacesExpr_.test(properties)) {
                bindProperties(object, properties.split(spacesExpr_), callback);
            } else {
                var property = properties;
                bindProperty(object, property, callback);
            }
        } else if (isArray(properties)) {
            bindProperties(object, properties, callback);
        }

        return async;
    };

    /**
     * The Binding.off definition.
     * @memberof Binding
     * @param {object} object Object target.
     * @param {?string|string[]} [properties] The name of property or array of names of property on which you wish to monitor changes.
     */
    Binding.off = function (object, properties) {
        if (typeof object === 'undefined' || !object) {
            return;
        }

        if (isHtmlElement(object)) {
            unbindHtmlElementProperties(object, properties);
        } else {
            if (typeof properties === 'string') {
                if (spacesExpr_.test(properties)) {
                    unbindProperties(object, properties.split(spacesExpr_));
                } else {
                    var property = properties;
                    unbindProperty(object, property);
                }
            } else {
                unbindProperties(object, properties);
            }
        }
    };

    if (typeof module !== 'undefined' && module) {
        module.exports = Binding;
    } else if (typeof exports !== 'undefined') {
        exports.Binding = Binding;
    } else {
        global.Binding = Binding;
    }

})(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window);
<commit_msg>bump to 0.0.12<commit_after>/**
 * @fileoverview Bind and watch any changes of object
 * @author Maxim Shaydo aka Max Graey <maxgraey@gmail.com>
 * @version 0.0.12
 */

(function (global) {
    'use strict';

    /**
     * Enum for data binding type.
     * @namespace Binding
     */
    var Binding = {};

    /**
     * One way binding
     * @constant
     */
    Binding.OneWay = 0;
    /**
     * Two way binding
     * @constant
     */
    //Binding.TwoWay = 1;
    /**
     * One time binding
     * @constant
     */
    //Binding.Once   = 2;

    /**************/
    /* poly-fills */
    /**************/

    /** @private */
    var filter = Array.prototype.filter.call.bind(Array.prototype.filter);

    /** @private */
    var MutationObserver = global.MutationObserver ||
                           global.WebKitMutationObserver ||
                           global.MozMutationObserver ||
                           global.OMutationObserver ||
                           global.MsMutationObserver;

    /** @private */
    var isArray = Array.isArray || function isArray(arr) {
        return arr !== undefined && arr.constructor === Array;
    };

    var setImmediate = global.setImmediate || (function () {
        var queue = [];
        var addEventListener = global.addEventListener || global.attachEvent;
        var canPost = typeof window !== 'undefined' && global.postMessage && addEventListener;

        if (canPost) {
            var message = global.addEventListener ? 'message' : 'onmessage';
            addEventListener(message, function (event) {
                if (event.source === window && event.data === 'immediate-message-id') {
                    event.stopPropagation();
                    if (queue.length > 0) {
                        var callback = queue.shift();
                        callback();
                    }
                }
            }, true);
        }

        return function (callback) {
            if (canPost) {
                queue.push(callback);
                global.postMessage('immediate-message-id', '*');
            } else {
                setTimeout(callback, 0);
            }
        };
    })();

    /**
     * WeakMap polyfill based on Andrea Giammarchi's solution
     * @private
     */
    var WeakMap = global.WeakMap || function () {
        var id = '_id:' + Math.random(), i = 0;

        function WeakMap() {
            Object.defineProperty(this, id, { value: id + i++ });
        }

        Object.defineProperties(WeakMap.prototype, {
            'has': { value: function (key) { return !!key[this[id]]; }},
            'get': { value: function (key) { return   key[this[id]]; }},
            'set': {
                value: function (key, value) {
                    Object.defineProperty(key, this[id], {
                        configurable: true,
                        value: value
                    });
                }
            },
            'delete': {
                value: function (key) {
                    var r = this.has(key);
                    if (r) { delete key[this[id]]; }
                    return r;
                }
            }
        });

        return WeakMap;
    };

    /****************************/
    /* private utillity methods */
    /****************************/

    /** @private */
    var observers_  = new WeakMap();
    var spacesExpr_ = /\s+/;
    var defaultObserveOptions_ = {
        subtree: true,
        childList: true,
        characterData: true,
        characterDataOldValue: true,
        attributes: true,
        attributeOldValue: true,
        attributeFilter: undefined
    };

    /** @private */
    function asyncWrap(callback, skipImmutables) {
        // for node.js
        if (typeof module !== 'undefined' && module.exports && process) {
            return function (target, property, newValue, oldValue) {
                if (skipImmutables && (newValue == oldValue || newValue === !!oldValue)) {
                    //console.log('skipeed');
                    return;
                }

                process.nextTick(function () {
                    callback(target, property, newValue, oldValue);
                });
            };
        } else {
            return function (target, property, newValue, oldValue) {
                if (skipImmutables && (newValue == oldValue || newValue === !!oldValue)) {
                    //console.log('skipeed');
                    return;
                }

                setImmediate(function () {
                    callback(target, property, newValue, oldValue);
                });
            };
        }
    }

    /** @private */
    function hasPropertyAccessors(object, property) { // has get or set
        var descriptor = Object.getOwnPropertyDescriptor(object, property);
        return !!descriptor && (!!descriptor.get || !!descriptor.set);
    }

     /** @private */
    function isPropertyConfigurable(propertyDescriptor) { // has get or set
        return !!propertyDescriptor && propertyDescriptor.configurable;
    }

    /** @private */
    function isHtmlElement(obj) {
        return (
            typeof HTMLElement === 'object' ?
                 obj instanceof HTMLElement :
                 typeof obj === 'object' && obj &&
                    obj.nodeType === 1 && typeof obj.nodeName === 'string'
        );
    }

    /** @private */
    function emptyProperties(properties) {
        return (typeof properties === 'undefined' || !properties ||
            (isArray(properties) && properties.length === 0));
    }

    /** @private */
    function descriptorForProperty(object, property, callback) {
        var descriptor = {
            oldValue: object[property],
            get: function () {
                return descriptor.oldValue;
            },
            set: function (value) {
                callback(object, property, value, descriptor.oldValue);
                descriptor.oldValue = value;
            },
            configurable: true,
            enumerable: true
        };

        return descriptor;
    }

    /** @private */
    function descriptorForAccessingProperty(object, property, callback) {
        var originalDesc = Object.getOwnPropertyDescriptor(object, property);
        var setter = function () {};
        var getter = function () {};

        if (!isPropertyConfigurable(originalDesc)) {
            throw new TypeError('The propery ' + property + ' is not configurable');
        }

        if (originalDesc) {
            if (typeof originalDesc.set === 'function') {
                setter = originalDesc.set;
            }

            if (typeof originalDesc.get === 'function') {
                getter = originalDesc.get;
            }
        }

        var descriptor = {
            oldValue: getter.call(object),
            get: getter,
            set: function (value) {
                var result = setter.call(object, value);
                callback(object, property, value, descriptor.oldValue);
                descriptor.oldValue = value;
                return result;
            },
            configurable: true,
            enumerable: true
        };

        return descriptor;
    }

    /** @private */
    function bindProperty(object, property, callback) {
        if (!(property in object)) {
            return;
        }

        var descriptor;
        if (hasPropertyAccessors(object, property)) {
            descriptor = descriptorForAccessingProperty(object, property, callback);
        } else {
            descriptor = descriptorForProperty(object, property, callback);
        }

        Object.defineProperty(object, property, descriptor);
    }

    /** @private */
    function unbindProperty(object, property) {
        if (object.hasOwnProperty(property)) {
            var value = object[property];
            delete object[property];
            object[property] = value;
        }
    }

    /** @private */
    function bindProperties(object, properties, callback) {
        if (emptyProperties(properties)) {
            properties = Object.keys(object);
        }

        for (var i = 0, len = properties.length; i < len; ++i) {
            bindProperty(object, properties[i], callback);
        }
    }

    /** @private */
    function unbindProperties(object, properties) {
        if (emptyProperties(properties)) {
            properties = Object.keys(object);
        }

        for (var i = 0, len = properties.length; i < len; ++i) {
            unbindProperty(object, properties[i]);
        }
    }

    /** @private */
    function validateArguments(object, callback) {
        if (typeof callback === 'undefined') {
            throw new ReferenceError('callback is not defined');
        }

        if (typeof object === 'undefined' || !object) {
           return false;
        }

        return true;
    }

    /** @private */
    function bindHtmlElementProperties(object, properties, callback, options) {
        if (!MutationObserver) {
            throw new Error('MutationObserver is not supported in your browser!');
        }

        var skipImmutables = options.skipImmutables;
        var handleEvents   = options.handleEvents;

        if (typeof handleEvents === 'undefined') {
            handleEvents = true;
        }

        if (typeof properties === 'string') {
            var expr = /\s+/;
            if (expr.test(properties)) {
                properties = properties.split(expr);
            } else {
                properties = [properties];
            }
        }

        if (isArray(properties) && properties.length === 0) {
            properties = undefined;
        }

        // add event changes
        if (handleEvents) {
            object.addEventListener('click', function (event) {
                callback(object, event.type, event, null);
            }, false);

            switch (object.nodeName) {
                case 'INPUT':
                case 'SELECT':
                    object.addEventListener('change', function (event) {
                        callback(object, event.type, event, null);
                    }, false);
                break;
                case 'TEXTAREA':
                    object.addEventListener('input', function (event) {
                        callback(object, event.type, event, null);
                    }, false);
                break;
            }
        }

        var observer = new MutationObserver(function (mutations) {
            var i, len;
            for (i = 0, len = mutations.length; i < len; ++i) {
                var mutation = mutations[i];
                //console.log(mutation);

                var property = mutation.type;
                var newValue = mutation.target.nodeValue;

                if (mutation.attributeName) {
                    property = mutation.attributeName;
                    newValue = object[property];

                    if (newValue && newValue.length) {
                        var value = {};
                        for (i = 0, len = value.length; i < len; ++i) {
                            var attribure    = newValue[i];
                            value[attribure] = newValue[attribure];
                        }
                        newValue = value;
                    }
                }

                if (skipImmutables && (newValue == mutation.oldValue || newValue === !!mutation.oldValue)) {
                    //console.log('skipeed');
                    return ;
                }

                callback(object, property, newValue, mutation.oldValue);
            }
        });

        var mutableOptions = Object.create(defaultObserveOptions_);

        mutableOptions.attributeFilter = properties;
        observer.observe(object, mutableOptions);
        observers_.set(object, { observer: observer, options: mutableOptions });
    }

    /** @private */
    function unbindHtmlElementProperties(object, properties) {
        var observerAndOptions = observers_.get(object);
        var observer = observerAndOptions.observer;
        var options  = observerAndOptions.options;

        observer.disconnect();
        if (emptyProperties(properties)) {
            observers_.delete(object);
        } else {
            if (typeof properties === 'string') {
                if (spacesExpr_.test(properties)) {
                    properties = properties.split(spacesExpr_);
                } else {
                    properties = [properties];
                }
            }

            var len = properties.length;
            var attributeFilter = options.attributeFilter;

            if (!attributeFilter) {
                attributeFilter = [];
                for (var attributeName in object) {
                    attributeFilter.push(attributeName);
                }
            }

            options.attributeFilter = filter(attributeFilter, function (property) {
                for (var i = 0; i < len; ++i) {
                    return (property !== properties[i]);
                }
            });

            observer.observe(object, options);
        }
    }

    /******************/
    /* Public methods */
    /******************/

    /**
     * The Binding.on definition.
     * @memberof Binding
     * @param {object} object Object target.
     * @param {?string|string[]} [properties] The name of property or array of names of property on which you wish to monitor changes.
     * @param {function} callback A function to call when the specified property's value changes.
     * @param {object}  [options] - options.
     * @param {number}  [options.bindingType] - Binding enum.
     * @param {boolean} [options.deep=false] - recursive watch all sub-object properties.
     * @param {boolean} [options.async=false] - asynchronous callback mode.
     * @param {boolean} [options.skipImmutables=true] - skip immutables changes in async mode.
     * @param {boolean} [options.handleEvents=true] - listen some DOMElement events (experimental)
     *
     * @return {boolean} True if callback is exectly asynchronous otherwise False.
     * @throws {Error|ReferenceError}
     */
    Binding.on = function (object, properties, callback, options) {
        if (!validateArguments(object, callback)) {
            return false;
        }

        if (typeof options !== 'object') {
            options = {};
        }

        var skipImmutables = options.skipImmutables;
        if (typeof skipImmutables === 'undefined') {
            skipImmutables = true;
        }

        var allProps    = (typeof properties === 'undefined' || !properties || !properties.length);
        var deep        = !!options.deep && allProps;
        var async       = !!options.async;
        var bindingType = options.bindingType || Binding.OneWay;

        if (async) {
            callback = asyncWrap(callback, skipImmutables);
        }

        if (isHtmlElement(object)) {
            bindHtmlElementProperties(object, properties, callback, options);
            return true;
        }

        // fast javascript object observing
        if (typeof properties === 'string') {
            if (spacesExpr_.test(properties)) {
                bindProperties(object, properties.split(spacesExpr_), callback);
            } else {
                var property = properties;
                bindProperty(object, property, callback);
            }
        } else if (isArray(properties)) {
            bindProperties(object, properties, callback);
        }

        return async;
    };

    /**
     * The Binding.off definition.
     * @memberof Binding
     * @param {object} object Object target.
     * @param {?string|string[]} [properties] The name of property or array of names of property on which you wish to monitor changes.
     */
    Binding.off = function (object, properties) {
        if (typeof object === 'undefined' || !object) {
            return;
        }

        if (isHtmlElement(object)) {
            unbindHtmlElementProperties(object, properties);
        } else {
            if (typeof properties === 'string') {
                if (spacesExpr_.test(properties)) {
                    unbindProperties(object, properties.split(spacesExpr_));
                } else {
                    var property = properties;
                    unbindProperty(object, property);
                }
            } else {
                unbindProperties(object, properties);
            }
        }
    };

    if (typeof module !== 'undefined' && module) {
        module.exports = Binding;
    } else if (typeof exports !== 'undefined') {
        exports.Binding = Binding;
    } else {
        global.Binding = Binding;
    }

})(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window);
<|endoftext|>


<commit_before>ButtonAdapter = {
	name: 'ButtonAdapter',

	handles : [
		'button'
	],

	toHTML: function(data) {
		var $div = $('<div>');
		var $a = $('a').appendTo($div);
		/*var $clear = */$('<div>', { style: 'clear: both' }).appendTo($div);
		$a.html(data.text);
		$a.attr('href', data.href);
		$a.attr('data-st-user-href', data['user-href']);

		Object.keys(data).forEach(function(e) {
			if (!/^css\-/.test(e))
				return;
			var cssKey = e.replace(/^css-/);
			var cssVal = data[e];
			$a.css(cssKey, cssVal);
		});

		$a.find('> *').css('margin', '0');

		return $div.html();
	},

	toJSON: function(html) {
		var $html = $(html);
		var $a = $html.find('a');

		var data = {};
		data.format = 'html';
		data.text = $a.html();
		data.href = $a.attr('href');
		data['user-href'] = $a.attr('data-st-user-href');
		$a.attr('style').split(';').forEach(function (e) {
			var v = e.split(':');
			var cssAttr = v[0];
			var cssVal = v[1];
			data['css-' + cssAttr] = cssVal;
		});

		return data;
	}
}

module.exports = ButtonAdapter;<commit_msg>ButtonAdapter: Add styles necessary for rendering<commit_after>ButtonAdapter = {
	name: 'ButtonAdapter',

	handles : [
		'button'
	],

	toHTML: function(data) {
		var $div = $('<div>');
		var $a = $('<a>').appendTo($div);
		/*var $clear = */$('<div>', { style: 'clear: both' }).appendTo($div);
		$a.html(data.text);
		$a.attr('href', data.href);
		$a.attr('data-st-user-href', data['user-href']);

		Object.keys(data).forEach(function(e) {
			if (!/^css\-/.test(e))
				return;
			var cssKey = e.replace(/^css-/, '');
			var cssVal = data[e];
			$a.css(cssKey, cssVal);
		});

		$a.css('overflow', 'hidden');
		$a.css('display', 'block');
		$a.css('line-height', 'normal');
		$a.css('box-sizing', 'border-box');
		$a.css('border-style', 'solid');
		$a.css('text-align', 'center');
		$a.css('margin', '0 auto');
		$a.find('> *').css('margin', '0');

		return $div.html();
	},

	toJSON: function(html) {
		var $html = $(html);
		var $a = $html.find('a');

		var data = {};
		data.format = 'html';
		data.text = $a.html();
		data.href = $a.attr('href');
		data['user-href'] = $a.attr('data-st-user-href');
		$a.attr('style').split(';').forEach(function (e) {
			var v = e.split(':');
			var cssAttr = v[0];
			var cssVal = v[1];
			data['css-' + cssAttr] = cssVal;
		});

		return data;
	}
}

module.exports = ButtonAdapter;<|endoftext|>


<commit_before>var AVM2 = (function () {

  function AVM2(builtinABC, mode) {
    if (!builtinABC) {
      throw new Error("Cannot initialize AVM2 without builtin.abc");
    }

    var sysDomain = new Domain(null, mode, true);
    sysDomain.executeAbc(new AbcFile(builtinABC), "builtin.abc");

    // TODO: this will change when we implement security domains.
    this.systemDomain = sysDomain;
    this.applicationDomain = new Domain(sysDomain, mode, false);
  }

  AVM2.prototype = {
    loadPlayerGlobal: function (playerGlobalSWF) {
      var sysDomain = this.systemDomain;
      // Load, but don't execute, the default player globals.
      SWF.parse(playerGlobalSWF, {
        oncomplete: function(result) {
          var tags = result.tags;
          for (var i = 0, n = tags.length; i < n; i++) {
            var tag = tags[i];
            if (tag.type === "abc") {
              sysDomain.loadAbc(new AbcFile(tag.data, "playerGlobal/library" + i + ".abc"));
            }
          }
        }
      });
    }
  };

  return AVM2;

})();
<commit_msg>Time playerGlobals.<commit_after>var AVM2 = (function () {

  function AVM2(builtinABC, mode) {
    if (!builtinABC) {
      throw new Error("Cannot initialize AVM2 without builtin.abc");
    }

    var sysDomain = new Domain(null, mode, true);
    sysDomain.executeAbc(new AbcFile(builtinABC), "builtin.abc");

    // TODO: this will change when we implement security domains.
    this.systemDomain = sysDomain;
    this.applicationDomain = new Domain(sysDomain, mode, false);
  }

  AVM2.prototype = {
    loadPlayerGlobal: function (playerGlobalSWF) {
      var sysDomain = this.systemDomain;
      // Load, but don't execute, the default player globals.
      Timer.start("Load Player Globals");
      SWF.parse(playerGlobalSWF, {
        oncomplete: function(result) {
          var tags = result.tags;
          for (var i = 0, n = tags.length; i < n; i++) {
            var tag = tags[i];
            if (tag.type === "abc") {
              sysDomain.loadAbc(new AbcFile(tag.data, "playerGlobal/library" + i + ".abc"));
            }
          }
        }
      });
      Timer.stop();
    }
  };

  return AVM2;

})();
<|endoftext|>


<commit_before>var sys, timeout = null;
const System = (function(){
  function System(input, gui){
    this.callback    = {};
    this.pages       = {};
    this.input       = input;
    this.gui         = gui;
    this.current = null;

    this.validCommand = [
       "join"
    ];
  }

  System.prototype.event = function(name, callback){
    if(typeof this.callback[name] === "undefined")
      this.callback[name] = [];
    this.callback[name].push(callback);
  }

  System.prototype.trigger = function(name, data){
    if(typeof this.callback[name] === "undefined")
      return;

    for(var i=0;i<this.callback[name].length;i++){
      this.callback[name][i].apply(this, data);
    }
  };

  System.prototype.inputText = function(){
     return this.input.value;
  };

  System.prototype.setInputText = function(text){
     this.input.value = text;
  };

  System.prototype.getPage = function(name){
    if(typeof this.pages[name] === "undefined")
      return null;
    return this.pages[name];
  };

  System.prototype.currentPage = function(){
    if(typeof this.pages[this.current] !== "undefined"){
      return this.pages[this.current];
    }
    return null;
  };

  System.prototype.appendPage = function(name){
     this.createPage(name);
     send(name, "/title");
     send(name, "/online");
  };

  System.prototype.createPage = function(name){
    //first push channel bottom to the top menu
    const buttom = createButtom(name, this);
    var page = new Page(
      name,
      buttom,
      new UserList(this),
      this.gui.initContextContainer(),
      this
    );
    this.pages[name] = page;

    document.getElementById("chat-top").appendChild(buttom);
    this.selectPage(name);
    return page;
  };

  System.prototype.selectPage = function(name){
    if(typeof this.pages[name] === "undefined"){
      return false;
    }

    if(typeof this.pages[this.current] !== "undefined"){
      this.pages[this.current].hide();
    }

    this.pages[name].show();
    this.current = name;
    return true;
  };

  System.prototype.isFocus = function(name){
    return this.current == name;
  };

  return System;
})();

function startChat(input, gui){
  sys = new System(input, gui);
  input.onkeyup = function(e){
    sys.trigger("input.keyup", [e]);
  };
  sys.event("input.keyup", handleInput);
  var page = sys.createPage("console");
  connect();
}

function connect(){
    stopUpdate();
    var ajax = new XMLHttpRequest();
    var chan;
    if(sys.currentPage().name == "console"){
      chan = "";
    }else{
      chan = "&channel="+sys.currentPage();
    }
    ajax.open("GET", "?_ajax=true"+chan, true);
    ajax.onreadystatechange = onAjaxRespons;
    ajax.send();
}

function onAjaxRespons(){
  if(this.readyState == 4){
    if(this.status != 200){
      sys.getPage("console").line("", "Ajax", "", "[color=red]"+encodeURI(this.responseText)+"[/color]");
      setUpdate();
      return;
    }
    var j;
    try{
      j = JSON.parse(this.responseText);
    }catch(e){
      if(e.responseText == "login"){
        location.reload();
        return;
      }else{
        sys.getPage("console").line("","","AjaxError", "[color=red]There happend a error: "+this.responseText+"[/color]");
        setUpdate();
        return;
      }
    }
    for(var i=0;i<j.message.length;i++){
       if(j.message[i].channel == "Bot"){
         if(j.message[i].message.indexOf("/ban") === 0){
           onMyBan(j.message[i]);
         }else if(j.message[i].message.indexOf("/kick") === 0){
           onMyKick(j.message[i]);
         }else if(j.message[i].message == "/exit"){
           location.reload();
         }else if(j.message[i].message.indexOf("/join") == 0){
           sys.createPage(j.message[i].message.substr(6));
         }else if(j.message[i].message.indexOf("/leave") == 0){
           onLeave(j.message[i].time, j.message[i].message.substr(7));
         }else if(j.message[i].message.indexOf("/avatar") == 0){
           document.getElementById("user-avatar").style.backgroundImage = "url('"+j.message[i].message.substr(8)+"')";
           sys.getPage("console").line(
             j.message[i].time,
             "",
             "Bot",
             language["onAvatar"]
           );
         }else{
           sys.getPage("console").onRespons(j.message[i]);
         }
       }else{
         const p = sys.getPage(j.message[i].channel);
         if(p == null){
           sys.getPage("console").line("", "", "Bot", "[color=red]Unknown channel "+j.message[i].channel+"[/color]");
           sys.getPage("console").line(j.message[i].time, "", j.message[i].nick, j.message[i].message);
           continue;
         }
         sys.getPage(j.message[i].channel).onRespons(j.message[i]);
       }
    }
    setUpdate();
  }
}

function setUpdate(){
  timeout = setTimeout(connect, updateFrame);
}

function stopUpdate(){
  if(timeout !== null){
    clearTimeout(timeout);
  }
}

function onMyBan(data){
  var channel = data.message.substr(5);
  var page = sys.getPage(channel);
  if(!page){
    sys.getPage("console").line(
      data.time,
      "",
      "Bot",
      "[color=red]Unknown channel: "+channel+"[/color]"
    );
    return;
  }
  page.remove();
  sys.getPage("console").line(
    data.time,
    "",
    "Bot",
    "[color=red]You are bannet from the channel "+channel+"[/color]"
  );
}

function onMyKick(data){
  var message = data.message.substr(6);
  var pos = message.indexOf(" ");
  var nick = message.substr(0, pos);
  message = message.substr(pos+1);
  pos = message.indexOf(" ");
  var channel = message.substr(0, pos);
  message = message.substr(pos+1);
  const page = sys.getPage(channel);
  if(page == null){
    sys.getPage("console").line(data.time, "", "Bot", "[color=red]Unknown channel: "+channel+"[/color]");
  }
  page.remove();
  delete sys.pages[channel];
  sys.getPage("console").line(
    data.time,
    "",
    "Bot",
    "[color=red]"+language["myKick"].replace("%nick", nick).replace("%channel", channel).replace("%message", message)+"[/color]"
  );
}

function onLeave(time, channel){
  const page = sys.getPage(channel);
  if(page == null){
    sys.getPage("console").line(time, "", "Bot", "[color=red]Unknown channel: "+channel+"[/color]");
    return;
  }
  page.remove();
  delete sys.pages[channel];
  sys.getPage("console").line(time, "", "Bot", "[color=red]"+language["myLeave"].replace("%channel", channel)+"[/color]");
}

function send(channel, message){
  stopUpdate();
  var ajax = new XMLHttpRequest();
  ajax.open("POST", "?_ajax=true&isPost=true&channel="+encodeURIComponent(channel), true);
  ajax.onreadystatechange = onAjaxRespons;
  ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  ajax.send("message="+encodeURIComponent(message));
}

//handle input from the input o.O
function handleInput(e){
  if(e.keyCode == 13 && this.inputText().trim().length != 0){
    const text = this.inputText();

    if(text.charAt(0) == "/"){
      var command;
      if((pos = text.indexOf(" ")) != -1){
        command = text.substr(1, pos-1);
      }else{
        command = text.substr(1);
      }
      if(sys.validCommand.indexOf(command) != -1){
         send("", text);
         this.setInputText("");
         return;
      }
    }

    if(this.currentPage().name == "console"){
       this.currentPage().line("", "", "Bot", "[color=red]"+language["writeConsole"]+"[/color]");
       this.setInputText("");
       return;
    }

     send(this.currentPage().name, this.inputText());
    this.setInputText("");
  }
}
<commit_msg>Update main.js<commit_after>var sys, timeout = null;
const System = (function(){
  function System(input, gui){
    this.callback    = {};
    this.pages       = {};
    this.input       = input;
    this.gui         = gui;
    this.current = null;

    this.validCommand = [
       "join"
    ];
  }

  System.prototype.event = function(name, callback){
    if(typeof this.callback[name] === "undefined")
      this.callback[name] = [];
    this.callback[name].push(callback);
  }

  System.prototype.trigger = function(name, data){
    if(typeof this.callback[name] === "undefined")
      return;

    for(var i=0;i<this.callback[name].length;i++){
      this.callback[name][i].apply(this, data);
    }
  };

  System.prototype.inputText = function(){
     return this.input.value;
  };

  System.prototype.setInputText = function(text){
     this.input.value = text;
  };

  System.prototype.getPage = function(name){
    if(typeof this.pages[name] === "undefined")
      return null;
    return this.pages[name];
  };

  System.prototype.currentPage = function(){
    if(typeof this.pages[this.current] !== "undefined"){
      return this.pages[this.current];
    }
    return null;
  };

  System.prototype.appendPage = function(name){
     this.createPage(name);
     send(name, "/title");
     send(name, "/online");
  };

  System.prototype.createPage = function(name){
    //first push channel bottom to the top menu
    const buttom = this.gui.createButtom(name, this);
    var page = new Page(
      name,
      buttom,
      new UserList(this),
      this.gui.initContextContainer(),
      this
    );
    this.pages[name] = page;

    document.getElementById("chat-top").appendChild(buttom);
    this.selectPage(name);
    return page;
  };

  System.prototype.selectPage = function(name){
    if(typeof this.pages[name] === "undefined"){
      return false;
    }

    if(typeof this.pages[this.current] !== "undefined"){
      this.pages[this.current].hide();
    }

    this.pages[name].show();
    this.current = name;
    return true;
  };

  System.prototype.isFocus = function(name){
    return this.current == name;
  };

  return System;
})();

function startChat(input, gui){
  sys = new System(input, gui);
  input.onkeyup = function(e){
    sys.trigger("input.keyup", [e]);
  };
  sys.event("input.keyup", handleInput);
  var page = sys.createPage("console");
  connect();
}

function connect(){
    stopUpdate();
    var ajax = new XMLHttpRequest();
    var chan;
    if(sys.currentPage().name == "console"){
      chan = "";
    }else{
      chan = "&channel="+sys.currentPage();
    }
    ajax.open("GET", "?_ajax=true"+chan, true);
    ajax.onreadystatechange = onAjaxRespons;
    ajax.send();
}

function onAjaxRespons(){
  if(this.readyState == 4){
    if(this.status != 200){
      sys.getPage("console").line("", "Ajax", "", "[color=red]"+encodeURI(this.responseText)+"[/color]");
      setUpdate();
      return;
    }
    var j;
    try{
      j = JSON.parse(this.responseText);
    }catch(e){
      if(e.responseText == "login"){
        location.reload();
        return;
      }else{
        sys.getPage("console").line("","","AjaxError", "[color=red]There happend a error: "+this.responseText+"[/color]");
        setUpdate();
        return;
      }
    }
    for(var i=0;i<j.message.length;i++){
       if(j.message[i].channel == "Bot"){
         if(j.message[i].message.indexOf("/ban") === 0){
           onMyBan(j.message[i]);
         }else if(j.message[i].message.indexOf("/kick") === 0){
           onMyKick(j.message[i]);
         }else if(j.message[i].message == "/exit"){
           location.reload();
         }else if(j.message[i].message.indexOf("/join") == 0){
           sys.createPage(j.message[i].message.substr(6));
         }else if(j.message[i].message.indexOf("/leave") == 0){
           onLeave(j.message[i].time, j.message[i].message.substr(7));
         }else if(j.message[i].message.indexOf("/avatar") == 0){
           document.getElementById("user-avatar").style.backgroundImage = "url('"+j.message[i].message.substr(8)+"')";
           sys.getPage("console").line(
             j.message[i].time,
             "",
             "Bot",
             language["onAvatar"]
           );
         }else{
           sys.getPage("console").onRespons(j.message[i]);
         }
       }else{
         const p = sys.getPage(j.message[i].channel);
         if(p == null){
           sys.getPage("console").line("", "", "Bot", "[color=red]Unknown channel "+j.message[i].channel+"[/color]");
           sys.getPage("console").line(j.message[i].time, "", j.message[i].nick, j.message[i].message);
           continue;
         }
         sys.getPage(j.message[i].channel).onRespons(j.message[i]);
       }
    }
    setUpdate();
  }
}

function setUpdate(){
  timeout = setTimeout(connect, updateFrame);
}

function stopUpdate(){
  if(timeout !== null){
    clearTimeout(timeout);
  }
}

function onMyBan(data){
  var channel = data.message.substr(5);
  var page = sys.getPage(channel);
  if(!page){
    sys.getPage("console").line(
      data.time,
      "",
      "Bot",
      "[color=red]Unknown channel: "+channel+"[/color]"
    );
    return;
  }
  page.remove();
  sys.getPage("console").line(
    data.time,
    "",
    "Bot",
    "[color=red]You are bannet from the channel "+channel+"[/color]"
  );
}

function onMyKick(data){
  var message = data.message.substr(6);
  var pos = message.indexOf(" ");
  var nick = message.substr(0, pos);
  message = message.substr(pos+1);
  pos = message.indexOf(" ");
  var channel = message.substr(0, pos);
  message = message.substr(pos+1);
  const page = sys.getPage(channel);
  if(page == null){
    sys.getPage("console").line(data.time, "", "Bot", "[color=red]Unknown channel: "+channel+"[/color]");
  }
  page.remove();
  delete sys.pages[channel];
  sys.getPage("console").line(
    data.time,
    "",
    "Bot",
    "[color=red]"+language["myKick"].replace("%nick", nick).replace("%channel", channel).replace("%message", message)+"[/color]"
  );
}

function onLeave(time, channel){
  const page = sys.getPage(channel);
  if(page == null){
    sys.getPage("console").line(time, "", "Bot", "[color=red]Unknown channel: "+channel+"[/color]");
    return;
  }
  page.remove();
  delete sys.pages[channel];
  sys.getPage("console").line(time, "", "Bot", "[color=red]"+language["myLeave"].replace("%channel", channel)+"[/color]");
}

function send(channel, message){
  stopUpdate();
  var ajax = new XMLHttpRequest();
  ajax.open("POST", "?_ajax=true&isPost=true&channel="+encodeURIComponent(channel), true);
  ajax.onreadystatechange = onAjaxRespons;
  ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  ajax.send("message="+encodeURIComponent(message));
}

//handle input from the input o.O
function handleInput(e){
  if(e.keyCode == 13 && this.inputText().trim().length != 0){
    const text = this.inputText();

    if(text.charAt(0) == "/"){
      var command;
      if((pos = text.indexOf(" ")) != -1){
        command = text.substr(1, pos-1);
      }else{
        command = text.substr(1);
      }
      if(sys.validCommand.indexOf(command) != -1){
         send("", text);
         this.setInputText("");
         return;
      }
    }

    if(this.currentPage().name == "console"){
       this.currentPage().line("", "", "Bot", "[color=red]"+language["writeConsole"]+"[/color]");
       this.setInputText("");
       return;
    }

     send(this.currentPage().name, this.inputText());
    this.setInputText("");
  }
}
<|endoftext|>


<commit_before>/*jslint node:true, nomen:true */
/*global unescape */

var fs = require('fs'), _ = require('lodash'), async = require('async'), crypto = require('crypto'),
modelFactory = require('./model'), controllerFactory = require('./controller'),
config, CONTROLLERS = './routes', MODELS = './models', reqLoader, textParser = require('./textParser'),
loadModel, loadController, trailSlash, trim, models = {}, controllers = {}, routes = {}, ctrlrRoutes = {
	base: {
		index: ["GET",false],
		show: ["GET",true],
		create: ["POST",false],
		update: ["PUT",true],
		patch: ["PATCH",true],
		destroy: ["DEL",true]
	},
	property: {
		getProperty: "GET",
		setProperty: "PUT"
	}
}, hashify = function (obj) {
	obj = obj || {};
	// get the keys and sort
	var keys = _.keys(obj).sort(), out = [];
	_.each(keys,function (k) {
		out.push(k+":"+typeof(obj[k]) === "object" ? hashify(obj[k]) : obj[k]);
	});
	out = crypto.createHash('md5').update(out.join(",")).digest("hex");
	return(out);
};

trailSlash = function (str) {
	return(str !== null && str !== undefined && typeof(str) === "string" && str.slice(-1) !== '/' ? str+'/' : str);
};
trim = function (str) {
	return((str||"").replace(/(^\s+|\s+$)/g,""));
};

reqLoader = function (req,res,next) {
	req.booster = {
		param : config.param,
		models : models,
		controllers : controllers
	};
	next();
};

loadModel = function(name,path,models,db) {
	var ret, module;
	/*jslint stupid:true */
	if (fs.existsSync(path+'.js')) {
    module = require(fs.realpathSync(path+'.js'));
		/*jslint stupid:false */
	} else {
		module = null;
	}
	ret = modelFactory.generate(name,models,db,module);
	return ret;
};
loadController = function(name,path,models,opts,config) {
	var ret, module;
	opts = opts || {};
	/*jslint stupid:true */
	if (fs.existsSync(path+name+'.js')) {
    module = require(fs.realpathSync(path+name+'.js'));
		/*jslint stupid:false */
	} else {
		module = null;
	}
	ret = controllerFactory.generate(name,module,opts);
	return ret;
};

module.exports = {
	init: function(conf) {
		config = _.extend({},conf);
		//booster.init({app:app,db:db,controllers:'./controllers',models:'./models'});
		// validate we have what we need
		config.controllers = trailSlash(config.controllers || CONTROLLERS);
		config.models = trailSlash(config.models || MODELS);
		config.param = config.param || {};
		config.base = config.base || "";
		// reset to be safe
		this.models = models = {};
		this.controllers = controllers = {};
		routes = {};

		// add the reqLoader
		conf.app.use(textParser);
		conf.app.use(reqLoader);

		return(this);
	},
	resource: function(name,opts) {
		// load and activate models and controllers for this resource, if they are available
		var m, c, app = config.app, basePath = "", adder = "", middleware = [reqLoader], processors = [];
		opts = opts || {};
		name = trim(name).replace(/^.*\//,"");
		if (name) {
			if (opts.parent) {
				basePath += routes[opts.parent] + "/:"+opts.parent + '/';
			} else if (opts.base) {
				basePath += opts.base;
			} else if (config.base) {
				basePath += config.base;
			}
			if (basePath.charAt(0) !== '/') {
				basePath = '/' + basePath;
			}
			if (basePath.slice(-1) !== '/') {
				basePath += '/';
			}
			if (!opts.root) {
				basePath += name;
				adder = '/';
			}
			models[name] = m = loadModel(name,config.models+name,models,config.db);
			controllers[name] = c = loadController(name,config.controllers,models,opts,config.param);
			routes[name] = basePath;

			// set up the middleware
			if (c.all && typeof(c.all) === "function") {
				middleware.push(c.all);
			}
			if (c.filter && c.filter.all && typeof(c.filter.all) === "function") {
				middleware.push(c.filter.all);
			}
			if (c.post && c.post.all && typeof(c.post.all) === "function") {
				processors.push(c.post.all);
			}
			// set up the routes using express routes
			_.each(ctrlrRoutes.base,function (route,method) {
				var verb = route[0].toLowerCase(), path = basePath+(route[1] ? adder+':'+name : ''), filter = (c.filter||{})[method],
				mw = [].concat(middleware,filter || []), post = (c.post||{})[method],
				pp = [].concat(processors,post || []);
				if (c[method] && typeof(c[method]) === "function") {
					app[verb](path,mw,function (req,res,next) {
						var r = res.send;
						res.send = function (status,body) {
							// undo what we did for res.send...
							res.send = r;
							if (pp && pp.length > 0) {
								async.each(pp,function(item,callback){
									item(req,res,callback,status,body);
								},function (err,data) {
									if (err) {
										next(err);
									} else {
										res.send(status,body);
									}
								});
							} else {
								res.send(status,body);
							}
						};
						c[method](req,res,next);
					});
				}
			});
			// special properties come first
			_.each(c.properties||{},function (handles,prop) {
				if (handles) {
					if (handles.get && typeof(handles.get) === "function") {
						app.get(basePath+adder+':'+name+'/'+prop,middleware,handles.get);
					}
					if (handles.set && typeof(handles.set) === "function") {
						app.put(basePath+adder+':'+name+'/'+prop,middleware,handles.set);
					}
				}
			});

			// and the special resource properties
			_.each(opts.resource,function (types,prop) {
				var fullPath = basePath+adder+':'+name+'/'+prop;
				if (_.contains(types,"get")) {
					app.get(fullPath,middleware,function (req,res,next) {
						// get the instances of 'prop' where the value of the field for our name matches our ID
						var search = {};
						search[name] = req.params[name];
						req.booster.models[prop].find(search,function (err,data) {
							if (err) {
								res.send(400,err);
							} else if (data && _.size(data) > 0) {
								res.send(200,data);
							} else {
								next();
							}
						});
					});
					if (_.contains(types,"set")) {
						app.put(fullPath,middleware,function (req,res,next) {
							var body = req.body ? [].concat(req.body) : [], search = {}, toRemove = [], toAdd = [],
							existing = {}, newList = {}, parentId = req.params[name],
							// *** SHOULD USE REFERENCED ID!! ****
							idField = "id",

							// before anything, we must make sure that each item to be added/saved has the key of our name === req.params[name]
							badParent = _.filter(body,function (entry) {
								return (!entry || !entry[name] || entry[name] !== parentId);
							});

							if (badParent && badParent.length > 0) {
								res.send(400,"field "+name+" in records must equal path value");
							} else {
								m = req.booster.models[prop];
								search[name] = parentId;
								m.find(search,function (err,data) {
									if (err) {
										res.send(400,err);
									} else {
										// hashify all the ones we found
										_.each(data,function (entry) {
											existing[hashify(_.omit(entry,idField))] = entry;
										});
										// hashify all of the new ones
										_.each(body,function (entry) {
											newList[hashify(_.omit(entry,idField))] = entry;
										});

										// toRemove are all of those that are in existing but not in newList
										_.each(existing,function (entry,key) {
											if (!newList[key]) {
												toRemove.push(entry[idField]);
											}
										});
										// toAdd are all of those in newList and NOT in existing
										_.each(newList,function (entry,key) {
											if (!existing[key]) {
												toAdd.push(entry);
											}
										});

										// now remove and add the ones we need
										async.series([
											function (cb) {
												async.each(toAdd,function (item,cb) {
													m.create(item,cb);
												},cb);
											},
											function (cb) {
												async.each(toRemove,function (id,cb) {
													m.destroy(id,cb);
												},cb);
											}
										],function (err,data) {
											if (err) {
												res.send(400,err);
											} else if (data) {
												res.send(200,data);
											} else {
												next();
											}
										});
									}
								});
							}
						});
					}
				}
			});

			_.each(ctrlrRoutes.property,function (route,method) {
				var verb = route.toLowerCase();
				if (c[method] && typeof(c[method] === "function")) {
					app[verb](basePath+adder+":"+name+'/:prop123var',middleware,c[method]);
				}
			});
		}
		return(this);
	},
	model: function(name) {
		models[name] = loadModel(name,config.models+name,models,config.db);
		return(this);
	},
	validator: require('./validator').validate,
	models: models,
	controllers: controllers
};
<commit_msg>Fix bug in hashify<commit_after>/*jslint node:true, nomen:true */
/*global unescape */

var fs = require('fs'), _ = require('lodash'), async = require('async'), crypto = require('crypto'),
modelFactory = require('./model'), controllerFactory = require('./controller'),
config, CONTROLLERS = './routes', MODELS = './models', reqLoader, textParser = require('./textParser'),
loadModel, loadController, trailSlash, trim, models = {}, controllers = {}, routes = {}, ctrlrRoutes = {
	base: {
		index: ["GET",false],
		show: ["GET",true],
		create: ["POST",false],
		update: ["PUT",true],
		patch: ["PATCH",true],
		destroy: ["DEL",true]
	},
	property: {
		getProperty: "GET",
		setProperty: "PUT"
	}
}, hashify = function (obj) {
	obj = obj || {};
	// get the keys and sort
	var keys = _.keys(obj).sort(), out = [];
	_.each(keys,function (k) {
		out.push(k+":"+(typeof(obj[k]) === "object" ? hashify(obj[k]) : obj[k]));
	});
	out = crypto.createHash('md5').update(out.join(",")).digest("hex");
	return(out);
};

trailSlash = function (str) {
	return(str !== null && str !== undefined && typeof(str) === "string" && str.slice(-1) !== '/' ? str+'/' : str);
};
trim = function (str) {
	return((str||"").replace(/(^\s+|\s+$)/g,""));
};

reqLoader = function (req,res,next) {
	req.booster = {
		param : config.param,
		models : models,
		controllers : controllers
	};
	next();
};

loadModel = function(name,path,models,db) {
	var ret, module;
	/*jslint stupid:true */
	if (fs.existsSync(path+'.js')) {
    module = require(fs.realpathSync(path+'.js'));
		/*jslint stupid:false */
	} else {
		module = null;
	}
	ret = modelFactory.generate(name,models,db,module);
	return ret;
};
loadController = function(name,path,models,opts,config) {
	var ret, module;
	opts = opts || {};
	/*jslint stupid:true */
	if (fs.existsSync(path+name+'.js')) {
    module = require(fs.realpathSync(path+name+'.js'));
		/*jslint stupid:false */
	} else {
		module = null;
	}
	ret = controllerFactory.generate(name,module,opts);
	return ret;
};

module.exports = {
	init: function(conf) {
		config = _.extend({},conf);
		//booster.init({app:app,db:db,controllers:'./controllers',models:'./models'});
		// validate we have what we need
		config.controllers = trailSlash(config.controllers || CONTROLLERS);
		config.models = trailSlash(config.models || MODELS);
		config.param = config.param || {};
		config.base = config.base || "";
		// reset to be safe
		this.models = models = {};
		this.controllers = controllers = {};
		routes = {};

		// add the reqLoader
		conf.app.use(textParser);
		conf.app.use(reqLoader);

		return(this);
	},
	resource: function(name,opts) {
		// load and activate models and controllers for this resource, if they are available
		var m, c, app = config.app, basePath = "", adder = "", middleware = [reqLoader], processors = [];
		opts = opts || {};
		name = trim(name).replace(/^.*\//,"");
		if (name) {
			if (opts.parent) {
				basePath += routes[opts.parent] + "/:"+opts.parent + '/';
			} else if (opts.base) {
				basePath += opts.base;
			} else if (config.base) {
				basePath += config.base;
			}
			if (basePath.charAt(0) !== '/') {
				basePath = '/' + basePath;
			}
			if (basePath.slice(-1) !== '/') {
				basePath += '/';
			}
			if (!opts.root) {
				basePath += name;
				adder = '/';
			}
			models[name] = m = loadModel(name,config.models+name,models,config.db);
			controllers[name] = c = loadController(name,config.controllers,models,opts,config.param);
			routes[name] = basePath;

			// set up the middleware
			if (c.all && typeof(c.all) === "function") {
				middleware.push(c.all);
			}
			if (c.filter && c.filter.all && typeof(c.filter.all) === "function") {
				middleware.push(c.filter.all);
			}
			if (c.post && c.post.all && typeof(c.post.all) === "function") {
				processors.push(c.post.all);
			}
			// set up the routes using express routes
			_.each(ctrlrRoutes.base,function (route,method) {
				var verb = route[0].toLowerCase(), path = basePath+(route[1] ? adder+':'+name : ''), filter = (c.filter||{})[method],
				mw = [].concat(middleware,filter || []), post = (c.post||{})[method],
				pp = [].concat(processors,post || []);
				if (c[method] && typeof(c[method]) === "function") {
					app[verb](path,mw,function (req,res,next) {
						var r = res.send;
						res.send = function (status,body) {
							// undo what we did for res.send...
							res.send = r;
							if (pp && pp.length > 0) {
								async.each(pp,function(item,callback){
									item(req,res,callback,status,body);
								},function (err,data) {
									if (err) {
										next(err);
									} else {
										res.send(status,body);
									}
								});
							} else {
								res.send(status,body);
							}
						};
						c[method](req,res,next);
					});
				}
			});
			// special properties come first
			_.each(c.properties||{},function (handles,prop) {
				if (handles) {
					if (handles.get && typeof(handles.get) === "function") {
						app.get(basePath+adder+':'+name+'/'+prop,middleware,handles.get);
					}
					if (handles.set && typeof(handles.set) === "function") {
						app.put(basePath+adder+':'+name+'/'+prop,middleware,handles.set);
					}
				}
			});

			// and the special resource properties
			_.each(opts.resource,function (types,prop) {
				var fullPath = basePath+adder+':'+name+'/'+prop;
				if (_.contains(types,"get")) {
					app.get(fullPath,middleware,function (req,res,next) {
						// get the instances of 'prop' where the value of the field for our name matches our ID
						var search = {};
						search[name] = req.params[name];
						req.booster.models[prop].find(search,function (err,data) {
							if (err) {
								res.send(400,err);
							} else if (data && _.size(data) > 0) {
								res.send(200,data);
							} else {
								next();
							}
						});
					});
					if (_.contains(types,"set")) {
						app.put(fullPath,middleware,function (req,res,next) {
							var body = req.body ? [].concat(req.body) : [], search = {}, toRemove = [], toAdd = [],
							existing = {}, newList = {}, parentId = req.params[name],
							// *** SHOULD USE REFERENCED ID!! ****
							idField = "id",

							// before anything, we must make sure that each item to be added/saved has the key of our name === req.params[name]
							badParent = _.filter(body,function (entry) {
								return (!entry || !entry[name] || entry[name] !== parentId);
							});

							if (badParent && badParent.length > 0) {
								res.send(400,"field "+name+" in records must equal path value");
							} else {
								m = req.booster.models[prop];
								search[name] = parentId;
								m.find(search,function (err,data) {
									if (err) {
										res.send(400,err);
									} else {
										// hashify all the ones we found
										_.each(data,function (entry) {
											existing[hashify(_.omit(entry,idField))] = entry;
										});
										// hashify all of the new ones
										_.each(body,function (entry) {
											newList[hashify(_.omit(entry,idField))] = entry;
										});

										// toRemove are all of those that are in existing but not in newList
										_.each(existing,function (entry,key) {
											if (!newList[key]) {
												toRemove.push(entry[idField]);
											}
										});
										// toAdd are all of those in newList and NOT in existing
										_.each(newList,function (entry,key) {
											if (!existing[key]) {
												toAdd.push(entry);
											}
										});

										// now remove and add the ones we need
										async.series([
											function (cb) {
												async.each(toAdd,function (item,cb) {
													m.create(item,cb);
												},cb);
											},
											function (cb) {
												async.each(toRemove,function (id,cb) {
													m.destroy(id,cb);
												},cb);
											}
										],function (err,data) {
											if (err) {
												res.send(400,err);
											} else if (data) {
												res.send(200,data);
											} else {
												next();
											}
										});
									}
								});
							}
						});
					}
				}
			});

			_.each(ctrlrRoutes.property,function (route,method) {
				var verb = route.toLowerCase();
				if (c[method] && typeof(c[method] === "function")) {
					app[verb](basePath+adder+":"+name+'/:prop123var',middleware,c[method]);
				}
			});
		}
		return(this);
	},
	model: function(name) {
		models[name] = loadModel(name,config.models+name,models,config.db);
		return(this);
	},
	validator: require('./validator').validate,
	models: models,
	controllers: controllers
};
<|endoftext|>


<commit_before>define(["text/type/Style","underscore"], function(Style,_) {

    return Style.inherit({

        _getUniqueFontName: function() {
            var font = this.$.font;
            if (font) {
                return font.getUniqueFontName();
            }

            return null;
        },

        _getColor: function() {
            var printTypeColor = this.$.printTypeColor;
            if (printTypeColor) {
                return printTypeColor.toHexString();
            }

            return null;
        },

        _getPrintColorId: function () {
            var printTypeColor = this.$.printTypeColor;
            if (printTypeColor) {
                return printTypeColor.$.id;
            }

            return null;
        },

        compose: function() {

            var ret = this.callBase();

            if (this.$.font) {
                ret.fontFamily = this._getUniqueFontName();
                ret.fontWeight = "normal"; // done via different font
                ret.fontStyle = "normal";
            }

            ret.color = this._getColor();
            ret.printColorId = this._getPrintColorId();

            delete ret.printTypeColor;
            delete ret.font;

            for (var key in ret) {
                if (ret.hasOwnProperty(key) && ret[key] === null) {
                    delete ret[key];
                }
            }

            return ret;
        },

        serialize: function() {
            var font = this.$.font,
                fontFamily = font.getFontFamily();

             return {
                fontFamilyId: fontFamily.$.id,
                fontId: font.$.id,
                printColorId: this._getPrintColorId(),
                fill: "#" + this._getColor(),
                fontFamily: font.$.name,
                fontWeight: font.$.weight,
                fontStyle: font.$.style,
                fontSize: this.$.fontSize
            };
        },

        clone: function(){
            return new this.factory(_.clone(this.$));
        }
    });

});<commit_msg>fixed bug with digital print colors<commit_after>define(["text/type/Style","underscore"], function(Style,_) {

    return Style.inherit({

        _getUniqueFontName: function() {
            var font = this.$.font;
            if (font) {
                return font.getUniqueFontName();
            }

            return null;
        },

        _getColor: function() {
            var printTypeColor = this.$.printTypeColor;
            if (printTypeColor) {
                return printTypeColor.toHexString();
            }

            return null;
        },

        _getPrintColorId: function () {
            var printTypeColor = this.$.printTypeColor;
            if (printTypeColor) {
                return printTypeColor.$.id;
            }

            return null;
        },

        compose: function() {

            var ret = this.callBase();

            if (this.$.font) {
                ret.fontFamily = this._getUniqueFontName();
                ret.fontWeight = "normal"; // done via different font
                ret.fontStyle = "normal";
            }

            ret.color = this._getColor();
            ret.printColorId = this._getPrintColorId();

            delete ret.printTypeColor;
            delete ret.font;

            for (var key in ret) {
                if (ret.hasOwnProperty(key) && ret[key] === null) {
                    delete ret[key];
                }
            }

            return ret;
        },

        serialize: function() {
            var font = this.$.font,
                fontFamily = font.getFontFamily();

             return {
                fontFamilyId: fontFamily.$.id,
                fontId: font.$.id,
                printColorId: this._getPrintColorId(),
                fill: this._getColor(),
                fontFamily: font.$.name,
                fontWeight: font.$.weight,
                fontStyle: font.$.style,
                fontSize: this.$.fontSize
            };
        },

        clone: function(){
            return new this.factory(_.clone(this.$));
        }
    });

});<|endoftext|>


<commit_before>import * as THREE from 'three';
import * as csp   from 'plexus-csp';

import * as util        from '../common/util';
import * as delaney     from '../dsymbols/delaney';
import * as tilings     from '../dsymbols/tilings';
import * as lattices    from '../geometry/lattices';
import * as periodic    from '../pgraphs/periodic';
import * as netSyms     from '../pgraphs/symmetries';
import {subD}           from '../graphics/surface';

import embed from '../pgraphs/embedding';

import { floatMatrices } from '../arithmetic/types';
const ops = floatMatrices;

const unitVec = v => ops.div(v, ops.norm(v));
const white = { hue: 0, saturation: 0, lightness: 1 };

const geometryNew = (vertices, faces) => {
  const normals = vertices.map(v => ops.times(v, 0));

  for (const f of faces) {
    const n = f.length;
    for (let i = 0; i < n; ++i) {
      const u = f[i];
      const v = f[(i + 1) % n];
      const w = f[(i + 2) % n];

      const a = ops.minus(vertices[u], vertices[v]);
      const b = ops.minus(vertices[w], vertices[v]);

      normals[v] = ops.plus(normals[v], ops.crossProduct(b, a));
    }
  }

  return {
    vertices: vertices.map((v, i) => ({ pos: v, normal: unitVec(normals[i]) })),
    faces: faces.map(f => ({ vertices: f, color: white })),
    isWireframe: false
  }
};

const geometry = (vertices, faces) => {
  const geom = new THREE.Geometry();

  vertices.forEach(v => {
    geom.vertices.push(new THREE.Vector3(v[0], v[1], v[2]));
  });

  faces.forEach(f => {
    f.forEach((v, i) => {
      if (i > 0 && i+1 < f.length)
        geom.faces.push(new THREE.Face3(f[0], f[i], f[i+1]));
    });
  });

  geom.computeFaceNormals();
  geom.computeVertexNormals();
  return geom;
};

const stick = (p, q, radius, segments) => {
  const normalized = v => ops.div(v, ops.norm(v));

  if (p.length == 2) {
    p = p.concat(0);
    q = q.concat(0);
  }

  const n = segments;
  const d = normalized(ops.minus(q, p));
  const ex = [1,0,0];
  const ey = [0,1,0];
  const t = Math.abs(ops.times(d, ex)) > 0.9 ? ey : ex;
  const u = normalized(ops.crossProduct(d, t));
  const v = normalized(ops.crossProduct(d, u));
  const a = Math.PI * 2 / n;

  const section = [];
  for (let i = 0; i < n; ++i) {
    const x = a * i;
    const c = Math.cos(x) * radius;
    const s = Math.sin(x) * radius;
    section.push(ops.plus(ops.times(c, u), ops.times(s, v)));
  }

  const vertices = [].concat(section.map(c => ops.plus(c, p)),
                             section.map(c => ops.plus(c, q)));

  const faces = new Array(n).fill(0).map((_, i) => {
    const j = (i + 1) % n;
    return [i, j, j+n, i+n];
  });

  const gNew = geometryNew(vertices, faces);

  return geometry(vertices, faces);
};

const makeBall = radius => {
  const t0 = {
    pos: [[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0], [0,0,-1]],
    faces : [[0,1,2], [1,0,5], [2,1,3], [0,2,4],
             [3,5,4], [5,3,1], [4,5,0], [3,4,2]],
    isFixed: [false, false, false, false, false, false]
  };
  const t = subD(subD(t0));

  return geometryNew(
    t.pos.map(v => ops.times(unitVec(v), radius)),
    t.faces
  );
};

const ballAndStick = (
  positions,
  edges,
  ballRadius=0.1,
  stickRadius=0.04,
  ballColor=0xe8d880,
  stickColor=0x404080
) => {
  const model = new THREE.Object3D();
  const ball  = new THREE.SphereGeometry(ballRadius, 16, 8);

  const bNew = makeBall(ballRadius);
  for (const i in bNew.vertices)
    console.log(`v${i}: ${JSON.stringify(bNew.vertices[i])}`);
  for (const i in bNew.faces)
    console.log(`f${i}: ${JSON.stringify(bNew.faces[i])}`);

  positions.forEach(p => {
    const mat = new THREE.MeshPhongMaterial({
      color: ballColor,
      shininess: 50
    });

    const s = new THREE.Mesh(ball, mat);
    s.position.x = p[0];
    s.position.y = p[1];
    s.position.z = p[2] || 0;
    model.add(s);
  });

  edges.forEach(e => {
    const u = positions[e[0]];
    const v = positions[e[1]];
    const s = stick(u, v, stickRadius, 8);
    s.computeVertexNormals();

    const mat = new THREE.MeshPhongMaterial({
      color: stickColor,
      shininess: 50
    });

    model.add(new THREE.Mesh(s, mat));
  });

  return model;
};

const _graphWithNormalizedShifts = graph => {
  const v0 = graph.edges[0].head;
  const adj = periodic.adjacencies(graph);
  const shifts = { [v0]: ops.vector(graph.dim) };
  const queue = [v0];

  while (queue.length) {
    const v = queue.shift();

    for (const { v: w, s } of adj[v]) {
      if (shifts[w] == null) {
        shifts[w] = ops.plus(s, shifts[v]);
        queue.push(w)
      }
    }
  }

  return periodic.make(graph.edges.map(e => {
    const h = e.head;
    const t = e.tail;
    const s = e.shift;

    return [h, t, ops.minus(shifts[t], ops.plus(shifts[h], s))];
  }));
};

const flatMap   = (fn, xs) => [].concat.apply([], xs.map(fn));

const cartesian = (...vs) => (
  vs.length == 0 ?
    [[]] :
    flatMap(xs => vs[vs.length - 1].map(y => xs.concat(y)),
            cartesian(...vs.slice(0, -1)))
);

const range = n => new Array(n).fill(0).map((_, i) => i);

const baseShifts = dim => dim == 3 ?
  cartesian([0, 1], [0, 1], [0, 1]) :
  cartesian(range(6), range(6));

const invariantBasis = gram => {
  const dot = (v, w) => ops.times(ops.times(v, gram), w);

  const vs = ops.identityMatrix(gram.length);
  const ortho = [];

  for (let v of vs) {
    for (const w of ortho)
      v = ops.minus(v, ops.times(w, dot(v, w)));
    ortho.push(ops.div(v, ops.sqrt(dot(v, v))))
  }

  return ops.times(gram, ops.transposed(ortho));
};

const makeNetModel = (structure, options, runJob, log) => csp.go(function*() {
  const graph = _graphWithNormalizedShifts(structure.graph);

  const embedding = embed(graph, !options.skipRelaxation);
  const basis = invariantBasis(embedding.gram);
  const pos = embedding.positions;

  const nodeIndex = {};
  const points = [];
  const edges = [];

  for (const s of baseShifts(graph.dim)) {
    for (const e of graph.edges) {
      edges.push([[e.head, s], [e.tail, ops.plus(s, e.shift)]].map(
        ([node, shift]) => {
          const key = JSON.stringify([node, shift]);
          const idx = nodeIndex[key] || points.length;
          if (idx == points.length) {
            points.push(ops.times(ops.plus(pos[node], shift), basis));
            nodeIndex[key] = idx;
          }
          return idx;
        }));
    }
  }

  return ballAndStick(points, edges);
});

const colorHSL = (hue, saturation, lightness) => {
  const c = new THREE.Color();
  c.setHSL(hue, saturation, lightness);
  return c;
};

const wireframe = (geometry, color) => {
  const wireframe = new THREE.WireframeGeometry(geometry);

  const line = new THREE.LineSegments(wireframe);
  line.material.color = color;

  return line;
};

const tilingModel = (
  surfaces, instances, options, basis, extensionFactor, shifts=[[0, 0, 0]]
) => {
  const model = new THREE.Object3D();
  const hue0 = Math.random();

  const geometries = surfaces.map(({ pos, faces }) => geometry(pos, faces));
  const extend = v => ops.times(v, extensionFactor);
  const dVecs = lattices.dirichletVectors(basis).map(extend);

  for (const i in instances) {
    const { templateIndex: kind, symmetry, center } = instances[i];
    const geom = geometries[kind];

    const matrix = new THREE.Matrix4();

    let A = symmetry;
    if (A.length == 3)
      A = [
        A[0].concat(0),
        A[1].concat(0),
        [0, 0, 1, 0],
        A[2].slice(0, 2).concat(0, 1)
      ];

    matrix.elements = [].concat.apply([], A);

    for (const s0 of shifts) {
      const a = options.colorByTranslationClass ?
        i / instances.length :
        kind / surfaces.length;

      const mat = new THREE.MeshPhongMaterial({
        color: colorHSL((hue0 + a) % 1, 1.0, 0.7),
        shininess: 15
      });

      const c = ops.plus(center, s0);
      const s = ops.plus(s0, lattices.shiftIntoDirichletDomain(c, dVecs));

      const tileMesh = new THREE.Mesh(geom, mat);
      tileMesh.applyMatrix(matrix);
      tileMesh.position.x += s[0];
      tileMesh.position.y += s[1];
      tileMesh.position.z += (s[2] || 0);
      model.add(tileMesh);

      if (options.showSurfaceMesh) {
        const tileWire = wireframe(geom, colorHSL(0.0, 0.0, 0.0));
        tileWire.applyMatrix(matrix);
        tileWire.position.x += s[0];
        tileWire.position.y += s[1];
        tileWire.position.z += (s[2] || 0);
        model.add(tileWire);
      }
    }
  }

  return model;
};

const light = (color, x, y, z) => {
  const light = new THREE.PointLight(color);

  light.position.set(x, y, z);

  return light;
};

const makeTilingModel =
  (structure, options, runJob, log) => csp.go(function*() {

  const ds = structure.symbol;
  const dim = delaney.dim(ds);
  const extensionFactor = dim == 3 ? 2 : 6;

  const t = util.timer();

  yield log('Finding the pseudo-toroidal cover...');
  const cov = yield structure.cover || (yield runJob({
    cmd: 'dsCover',
    val: ds
  }));
  console.log(`${Math.round(t())} msec to compute the cover`);

  yield log('Extracting the skeleton...');
  const skel = yield runJob({
    cmd: 'skeleton',
    val: cov
  });
  console.log(`${Math.round(t())} msec to extract the skeleton`);

  yield log('Computing an embedding...');
  const embedding = yield runJob({
    cmd: 'embedding',
    val: { graph: skel.graph, relax: !options.skipRelaxation }
  });
  const pos = embedding.positions;
  console.log(`${Math.round(t())} msec to compute the embedding`);

  yield log('Computing a translation basis...');
  const basis = yield invariantBasis(embedding.gram);
  console.log(`${Math.round(t())} msec to compute the translation basis`);

  yield log('Making the base tile surfaces...');
  const { templates, tiles } = yield runJob({
    cmd: 'tileSurfaces',
    val: { ds, cov, skel, pos, basis }
  });
  console.log(`${Math.round(t())} msec to make the base surfaces`);

  yield log('Refining the tile surfaces...');
  const refinedTemplates = yield runJob({
    cmd: 'processSolids',
    val: templates.map(({ pos, faces }) => ({
      pos,
      faces,
      isFixed: pos.map(_ => true),
      subDLevel: options.extraSmooth ? 3 : 2
    }))
  });
  console.log(`${Math.round(t())} msec to refine the surfaces`);

  yield log('Making the tiling geometry...');
  const shifts = baseShifts(dim).map(s => ops.times(s, basis));
  const model = tilingModel(
    refinedTemplates, tiles, options, basis, extensionFactor, shifts);
  console.log(`${Math.round(t())} msec to make the tiling geometry`);

  return model;
});

const builders = {
  tiling        : makeTilingModel,
  periodic_graph: makeNetModel,
  net           : makeNetModel,
  crystal       : makeNetModel
};

const makeScene = (structure, options, runJob, log) => csp.go(function*() {
  const type = structure.type;
  const builder = builders[type];

  if (builder == null)
    throw new Error(`rendering not implemented for type ${type}`);

  const model = yield builder(structure, options, runJob, log);

  const bbox = new THREE.Box3();
  bbox.setFromObject(model);
  model.position.sub(bbox.getCenter(new THREE.Vector3()));

  log('Composing the scene...');

  const distance = 12;
  const camera = new THREE.PerspectiveCamera(25, 1, 0.1, 10000);
  camera.name = 'camera';
  camera.position.z = distance;

  camera.add(light(0xaaaaaa,  distance, 0.5*distance, distance));
  camera.add(light(0x555555, -0.5*distance, -0.25*distance, distance));
  camera.add(light(0x000033, 0.25*distance, 0.25*distance, -distance));

  const scene = new THREE.Scene();

  scene.add(model);
  scene.add(camera);

  log('Scene complete!');
  return scene;
});

export default makeScene;
<commit_msg>finish converting ball-and-stick scene builder code<commit_after>import * as THREE from 'three';
import * as csp   from 'plexus-csp';

import * as util        from '../common/util';
import * as delaney     from '../dsymbols/delaney';
import * as tilings     from '../dsymbols/tilings';
import * as lattices    from '../geometry/lattices';
import * as periodic    from '../pgraphs/periodic';
import * as netSyms     from '../pgraphs/symmetries';
import {subD}           from '../graphics/surface';

import embed from '../pgraphs/embedding';

import { floatMatrices } from '../arithmetic/types';
const ops = floatMatrices;

const unitVec = v => ops.div(v, ops.norm(v));
const white = { hue: 0, saturation: 0, lightness: 1 };

const geometry = (vertices, faces, isWireframe=false) => {
  const normals = vertices.map(v => ops.times(v, 0));

  for (const f of faces) {
    const n = f.length;
    for (let i = 0; i < n; ++i) {
      const u = f[i];
      const v = f[(i + 1) % n];
      const w = f[(i + 2) % n];

      const a = ops.minus(vertices[u], vertices[v]);
      const b = ops.minus(vertices[w], vertices[v]);

      normals[v] = ops.plus(normals[v], ops.crossProduct(b, a));
    }
  }

  return {
    vertices: vertices.map((v, i) => ({
      pos: v,
      normal: unitVec(normals[i])
    })),
    faces: faces.map(f => ({
      vertices: f,
      color: white
    })),
    isWireframe: isWireframe
  }
};

const stick = (p, q, radius, segments) => {
  const normalized = v => ops.div(v, ops.norm(v));

  if (p.length == 2) {
    p = p.concat(0);
    q = q.concat(0);
  }

  const n = segments;
  const d = normalized(ops.minus(q, p));
  const ex = [1,0,0];
  const ey = [0,1,0];
  const t = Math.abs(ops.times(d, ex)) > 0.9 ? ey : ex;
  const u = normalized(ops.crossProduct(d, t));
  const v = normalized(ops.crossProduct(d, u));
  const a = Math.PI * 2 / n;

  const section = [];
  for (let i = 0; i < n; ++i) {
    const x = a * i;
    const c = Math.cos(x) * radius;
    const s = Math.sin(x) * radius;
    section.push(ops.plus(ops.times(c, u), ops.times(s, v)));
  }

  const vertices = [].concat(section.map(c => ops.plus(c, p)),
                             section.map(c => ops.plus(c, q)));

  const faces = new Array(n).fill(0).map((_, i) => {
    const j = (i + 1) % n;
    return [i, j, j+n, i+n];
  });

  return geometry(vertices, faces);
};

const makeBall = radius => {
  const t0 = {
    pos: [[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0], [0,0,-1]],
    faces : [[0,1,2], [1,0,5], [2,1,3], [0,2,4],
             [3,5,4], [5,3,1], [4,5,0], [3,4,2]],
    isFixed: [false, false, false, false, false, false]
  };
  const t = subD(subD(t0));

  return geometry(
    t.pos.map(v => ops.times(unitVec(v), radius)),
    t.faces
  );
};

const baseMaterial = {
  ambientColor: white,
  diffuseColor: white,
  specularColor: white,
  ka: 0.1,
  kd: 1.0,
  ks: 0.2,
  shininess: 15.0
};

const ballAndStick = (
  positions,
  edges,
  ballRadius=0.1,
  stickRadius=0.04,
  ballColor={ hue: 0.9, saturation: 0.7, lightness: 0.7 },
  stickColor={ hue: 4.2, saturation: 0.3, lightness: 0.4 }
) => {
  const ball = makeBall(ballRadius);

  const meshes = [ ball ];
  const instances = [];

  const ballMaterial = Object.assign({}, baseMaterial, {
    diffuseColor: ballColor,
    shininess: 50.0
  });

  const stickMaterial = Object.assign({}, baseMaterial, {
    diffuseColor: stickColor,
    shininess: 50.0
  });

  positions.forEach(p => {
    instances.push({
      meshIndex: 0,
      material: ballMaterial,
      transform: {
        basis: [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ],
        shift: [ p[0], p[1], p[2] || 0 ]
      }
    })
  });

  edges.forEach(e => {
    const u = positions[e[0]];
    const v = positions[e[1]];

    meshes.push(stick(u, v, stickRadius, 8));

    instances.push({
      meshIndex: meshes.length - 1,
      material: stickMaterial,
      transform: {
        basis: [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ],
        shift: [ 0, 0, 0 ]
      }
    })
  });

  return { meshes, instances };
};

const _graphWithNormalizedShifts = graph => {
  const v0 = graph.edges[0].head;
  const adj = periodic.adjacencies(graph);
  const shifts = { [v0]: ops.vector(graph.dim) };
  const queue = [v0];

  while (queue.length) {
    const v = queue.shift();

    for (const { v: w, s } of adj[v]) {
      if (shifts[w] == null) {
        shifts[w] = ops.plus(s, shifts[v]);
        queue.push(w)
      }
    }
  }

  return periodic.make(graph.edges.map(e => {
    const h = e.head;
    const t = e.tail;
    const s = e.shift;

    return [h, t, ops.minus(shifts[t], ops.plus(shifts[h], s))];
  }));
};

const flatMap   = (fn, xs) => [].concat.apply([], xs.map(fn));

const cartesian = (...vs) => (
  vs.length == 0 ?
    [[]] :
    flatMap(xs => vs[vs.length - 1].map(y => xs.concat(y)),
            cartesian(...vs.slice(0, -1)))
);

const range = n => new Array(n).fill(0).map((_, i) => i);

const baseShifts = dim => dim == 3 ?
  cartesian([0, 1], [0, 1], [0, 1]) :
  cartesian(range(6), range(6));

const invariantBasis = gram => {
  const dot = (v, w) => ops.times(ops.times(v, gram), w);

  const vs = ops.identityMatrix(gram.length);
  const ortho = [];

  for (let v of vs) {
    for (const w of ortho)
      v = ops.minus(v, ops.times(w, dot(v, w)));
    ortho.push(ops.div(v, ops.sqrt(dot(v, v))))
  }

  return ops.times(gram, ops.transposed(ortho));
};

const makeNetModel = (structure, options, runJob, log) => csp.go(function*() {
  const graph = _graphWithNormalizedShifts(structure.graph);

  const embedding = embed(graph, !options.skipRelaxation);
  const basis = invariantBasis(embedding.gram);
  const pos = embedding.positions;

  const nodeIndex = {};
  const points = [];
  const edges = [];

  for (const s of baseShifts(graph.dim)) {
    for (const e of graph.edges) {
      edges.push([[e.head, s], [e.tail, ops.plus(s, e.shift)]].map(
        ([node, shift]) => {
          const key = JSON.stringify([node, shift]);
          const idx = nodeIndex[key] || points.length;
          if (idx == points.length) {
            points.push(ops.times(ops.plus(pos[node], shift), basis));
            nodeIndex[key] = idx;
          }
          return idx;
        }));
    }
  }

  return ballAndStick(points, edges);
});

const colorHSL = (hue, saturation, lightness) => {
  const c = new THREE.Color();
  c.setHSL(hue, saturation, lightness);
  return c;
};

const wireframe = (geometry, color) => {
  const wireframe = new THREE.WireframeGeometry(geometry);

  const line = new THREE.LineSegments(wireframe);
  line.material.color = color;

  return line;
};

const tilingModel = (
  surfaces, instances, options, basis, extensionFactor, shifts=[[0, 0, 0]]
) => {
  const model = new THREE.Object3D();
  const hue0 = Math.random();

  const geometries = surfaces.map(({ pos, faces }) => geometry(pos, faces));
  const extend = v => ops.times(v, extensionFactor);
  const dVecs = lattices.dirichletVectors(basis).map(extend);

  for (const i in instances) {
    const { templateIndex: kind, symmetry, center } = instances[i];
    const geom = geometries[kind];

    const matrix = new THREE.Matrix4();

    let A = symmetry;
    if (A.length == 3)
      A = [
        A[0].concat(0),
        A[1].concat(0),
        [0, 0, 1, 0],
        A[2].slice(0, 2).concat(0, 1)
      ];

    matrix.elements = [].concat.apply([], A);

    for (const s0 of shifts) {
      const a = options.colorByTranslationClass ?
        i / instances.length :
        kind / surfaces.length;

      const mat = new THREE.MeshPhongMaterial({
        color: colorHSL((hue0 + a) % 1, 1.0, 0.7),
        shininess: 15
      });

      const c = ops.plus(center, s0);
      const s = ops.plus(s0, lattices.shiftIntoDirichletDomain(c, dVecs));

      const tileMesh = new THREE.Mesh(geom, mat);
      tileMesh.applyMatrix(matrix);
      tileMesh.position.x += s[0];
      tileMesh.position.y += s[1];
      tileMesh.position.z += (s[2] || 0);
      model.add(tileMesh);

      if (options.showSurfaceMesh) {
        const tileWire = wireframe(geom, colorHSL(0.0, 0.0, 0.0));
        tileWire.applyMatrix(matrix);
        tileWire.position.x += s[0];
        tileWire.position.y += s[1];
        tileWire.position.z += (s[2] || 0);
        model.add(tileWire);
      }
    }
  }

  return model;
};

const light = (color, x, y, z) => {
  const light = new THREE.PointLight(color);

  light.position.set(x, y, z);

  return light;
};

const makeTilingModel =
  (structure, options, runJob, log) => csp.go(function*() {

  const ds = structure.symbol;
  const dim = delaney.dim(ds);
  const extensionFactor = dim == 3 ? 2 : 6;

  const t = util.timer();

  yield log('Finding the pseudo-toroidal cover...');
  const cov = yield structure.cover || (yield runJob({
    cmd: 'dsCover',
    val: ds
  }));
  console.log(`${Math.round(t())} msec to compute the cover`);

  yield log('Extracting the skeleton...');
  const skel = yield runJob({
    cmd: 'skeleton',
    val: cov
  });
  console.log(`${Math.round(t())} msec to extract the skeleton`);

  yield log('Computing an embedding...');
  const embedding = yield runJob({
    cmd: 'embedding',
    val: { graph: skel.graph, relax: !options.skipRelaxation }
  });
  const pos = embedding.positions;
  console.log(`${Math.round(t())} msec to compute the embedding`);

  yield log('Computing a translation basis...');
  const basis = yield invariantBasis(embedding.gram);
  console.log(`${Math.round(t())} msec to compute the translation basis`);

  yield log('Making the base tile surfaces...');
  const { templates, tiles } = yield runJob({
    cmd: 'tileSurfaces',
    val: { ds, cov, skel, pos, basis }
  });
  console.log(`${Math.round(t())} msec to make the base surfaces`);

  yield log('Refining the tile surfaces...');
  const refinedTemplates = yield runJob({
    cmd: 'processSolids',
    val: templates.map(({ pos, faces }) => ({
      pos,
      faces,
      isFixed: pos.map(_ => true),
      subDLevel: options.extraSmooth ? 3 : 2
    }))
  });
  console.log(`${Math.round(t())} msec to refine the surfaces`);

  yield log('Making the tiling geometry...');
  const shifts = baseShifts(dim).map(s => ops.times(s, basis));
  const model = tilingModel(
    refinedTemplates, tiles, options, basis, extensionFactor, shifts);
  console.log(`${Math.round(t())} msec to make the tiling geometry`);

  return model;
});

const builders = {
  tiling        : makeTilingModel,
  periodic_graph: makeNetModel,
  net           : makeNetModel,
  crystal       : makeNetModel
};

const makeScene = (structure, options, runJob, log) => csp.go(function*() {
  const type = structure.type;
  const builder = builders[type];

  if (builder == null)
    throw new Error(`rendering not implemented for type ${type}`);

  const model = yield builder(structure, options, runJob, log);

  for (const mesh of model.meshes)
    console.log(JSON.stringify(mesh));

  for (const instance of model.instances)
    console.log(JSON.stringify(instance));

  //const bbox = new THREE.Box3();
  //bbox.setFromObject(model);
  //model.position.sub(bbox.getCenter(new THREE.Vector3()));

  log('Scene complete!');
  return model;
});

export default makeScene;
<|endoftext|>