<commit_before>//
//  ____  _                           _
// / ___|| |_ __ _ _ __   ___  ___   (_)___  (*)
// \___ \| __/ _` | '_ \ / _ \/ __|  | / __|
//  ___) | || (_| | |_) |  __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
//              |_|              |__/
//
// (*) a (really) tiny Javascript MVC microframework
//
// (c) Hay Kranen < hay@bykr.org >
// Released under the terms of the MIT license
// < http://en.wikipedia.org/wiki/MIT_License >
//
// Stapes.js : http://hay.github.com/stapes

(function() {
    'use strict';

    var VERSION = "0.6.1";

    // Global counter for all events in all modules (including mixed in objects)
    var guid = 1;

    // Makes _.create() faster
    var CachedFunction = function(){};

    // Private attributes and helper functions, stored in an object so they
    // are overwritable by plugins
    var _ = {
        // Properties
        attributes : {},

        eventHandlers : {
            "-1" : {} // '-1' is used for the global event handling
        },

        guid : -1,

        // Methods
        addEvent : function(event) {
            // If we don't have any handlers for this type of event, add a new
            // array we can use to push new handlers
            if (!_.eventHandlers[event.guid][event.type]) {
                _.eventHandlers[event.guid][event.type] = [];
            }

            // Push an event object
            _.eventHandlers[event.guid][event.type].push({
                "guid" : event.guid,
                "handler" : event.handler,
                "scope" : event.scope,
                "type" : event.type
            });
        },

        addEventHandler : function(argTypeOrMap, argHandlerOrScope, argScope) {
            var eventMap = {},
                scope;

            if (typeof argTypeOrMap === "string") {
                scope = argScope || false;
                eventMap[ argTypeOrMap ] = argHandlerOrScope;
            } else {
                scope = argHandlerOrScope || false;
                eventMap = argTypeOrMap;
            }

            for (var eventString in eventMap) {
                var handler = eventMap[eventString];
                var events = eventString.split(" ");

                for (var i = 0, l = events.length; i < l; i++) {
                    var eventType = events[i];
                    _.addEvent.call(this, {
                        "guid" : this._guid || this._.guid,
                        "handler" : handler,
                        "scope" : scope,
                        "type" : eventType
                    });
                }
            }
        },

        addGuid : function(object, forceGuid) {
            if (object._guid && !forceGuid) return;

            object._guid = guid++;

            _.attributes[object._guid] = {};
            _.eventHandlers[object._guid] = {};
        },

        // This is a really small utility function to save typing and produce
        // better optimized code
        attr : function(guid) {
            return _.attributes[guid];
        },

        clone : function(obj) {
            return _.extend({}, obj);
        },

        create : function(obj) {
            CachedFunction.prototype = obj;
            return new CachedFunction();
        },

        // Stapes objects have some extra properties that are set on creation
        createModule : function( context ) {
            var instance = _.create( context );

            _.addGuid( instance, true );

            // Mixin events
            Stapes.mixinEvents( instance );

            return instance;
        },

        emitEvents : function(type, data, explicitType, explicitGuid) {
            explicitType = explicitType || false;
            explicitGuid = explicitGuid || this._guid;

            var handlers = _.eventHandlers[explicitGuid][type];

            for (var i = 0, l = handlers.length; i < l; i++) {
                // Clone the event to prevent issue #19
                var event = _.extend({}, handlers[i]);
                var scope = (event.scope) ? event.scope : this;

                if (explicitType) {
                    event.type = explicitType;
                }

                event.scope = scope;
                event.handler.call(event.scope, data, event);
            }
        },

        extend : function(obj, props) {
            for (var key in props) {
                obj[key] = props[key];
            }

            return obj;
        },

        // from http://stackoverflow.com/a/2117523/152809
        makeUuid : function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        },

        removeAttribute : function(keys, silent) {
            silent = silent || false;

            // Split the key, maybe we want to remove more than one item
            var attributes = _.trim(keys).split(" ");

            // Actually delete the item
            for (var i = 0, l = attributes.length; i < l; i++) {
                var key = _.trim(attributes[i]);

                if (key) {
                    delete _.attr(this._guid)[key];

                    // If 'silent' is set, do not throw any events
                    if (!silent) {
                        this.emit('change', key);
                        this.emit('change:' + key);
                        this.emit('remove', key);
                        this.emit('remove:' + key);
                    }
                }
            }
        },

        removeEventHandler : function(type, handler) {
            var handlers = _.eventHandlers[this._guid];

            if (type && handler) {
                // Remove a specific handler
                handlers = handlers[type];
                if (!handlers) return;

                for (var i = 0, l = handlers.length, h; i < l; i++) {
                    h = handlers[i].handler;
                    if (h && h === handler) {
                        handlers.splice(i--, 1);
                        l--;
                    }
                }
            } else if (type) {
                // Remove all handlers for a specific type
                delete handlers[type];
            } else {
                // Remove all handlers for this module
                _.eventHandlers[this._guid] = {};
            }
        },

        setAttribute : function(key, value, silent) {
            silent = silent || false;

            // We need to do this before we actually add the item :)
            var itemExists = this.has(key);
            var oldValue = _.attr(this._guid)[key];

            // Is the value different than the oldValue? If not, ignore this call
            if (value === oldValue) {
                return;
            }

            // Actually add the item to the attributes
            _.attr(this._guid)[key] = value;

            // If 'silent' flag is set, do not throw any events
            if (silent) {
                return;
            }

            // Throw a generic event
            this.emit('change', key);

            // And a namespaced event as well, NOTE that we pass value instead of
            // key here!
            this.emit('change:' + key, value);

            // Throw namespaced and non-namespaced 'mutate' events as well with
            // the old value data as well and some extra metadata such as the key
            var mutateData = {
                "key" : key,
                "newValue" : value,
                "oldValue" : oldValue || null
            };

            this.emit('mutate', mutateData);
            this.emit('mutate:' + key, mutateData);

            // Also throw a specific event for this type of set
            var specificEvent = itemExists ? 'update' : 'create';

            this.emit(specificEvent, key);

            // And a namespaced event as well, NOTE that we pass value instead of key
            this.emit(specificEvent + ':' + key, value);
        },

        trim : function(str) {
            return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        },

        typeOf : function(val) {
            if (val === null || typeof val === "undefined") {
                return String(val);
            } else {
                return Object.prototype.toString.call(val).replace(/\[object |\]/g, '').toLowerCase();
            }
        },

        updateAttribute : function(key, fn) {
            var item = this.get(key);
            var newValue = fn.call(this, _.clone(item), key);
            _.setAttribute.call(this, key, newValue, silent || false);
        }
    };

    // Can be mixed in later using Stapes.mixinEvents(object);
    var Events = {
        emit : function(types, data) {
            data = (typeof data === "undefined") ? null : data;

            var splittedTypes = types.split(" ");

            for (var i = 0, l = splittedTypes.length; i < l; i++) {
                var type = splittedTypes[i];

                // First 'all' type events: is there an 'all' handler in the
                // global stack?
                if (_.eventHandlers[-1].all) {
                    _.emitEvents.call(this, "all", data, type, -1);
                }

                // Catch all events for this type?
                if (_.eventHandlers[-1][type]) {
                    _.emitEvents.call(this, type, data, type, -1);
                }

                if (typeof this._guid === 'number') {
                    // 'all' event for this specific module?
                    if (_.eventHandlers[this._guid].all) {
                        _.emitEvents.call(this, "all", data, type);
                    }

                    // Finally, normal events :)
                    if (_.eventHandlers[this._guid][type]) {
                        _.emitEvents.call(this, type, data);
                    }
                }
            }
        },

        off : function() {
            _.removeEventHandler.apply(this, arguments);
        },

        on : function() {
            _.addEventHandler.apply(this, arguments);
        }
    };

    _.Module = {
        create : function() {
            return _.createModule( this );
        },

        each : function(fn, ctx) {
            var attr = _.attr(this._guid);
            for (var key in attr) {
                var value = attr[key];
                fn.call(ctx || this, value, key);
            }
        },

        extend : function(objectOrValues, valuesIfObject) {
            var object = (valuesIfObject) ? objectOrValues : this;
            var values = (valuesIfObject) ? valuesIfObject : objectOrValues;

            _.extend(object, values);

            return this;
        },

        filter : function(fn) {
            var filtered = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                if ( fn.call(this, attributes[key], key)) {
                    filtered.push( attributes[key] );
                }
            }

            return filtered;
        },

        get : function(input) {
            if (typeof input === "string") {
                return this.has(input) ? _.attr(this._guid)[input] : null;
            } else if (typeof input === "function") {
                var items = this.filter(input);
                return (items.length) ? items[0] : null;
            }
        },

        getAll : function() {
            return _.clone( _.attr(this._guid) );
        },

        getAllAsArray : function() {
            var arr = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                var value = attributes[key];

                if (_.typeOf(value) === "object" && !value.id) {
                    value.id = key;
                }

                arr.push(value);
            }

            return arr;
        },

        has : function(key) {
            return (typeof _.attr(this._guid)[key] !== "undefined");
        },

        // Akin to set(), but makes a unique id
        push : function(input, silent) {
            if (_.typeOf(input) === "array") {
                for (var i = 0, l = input.length; i < l; i++) {
                    _.setAttribute.call(this, _.makeUuid(), input[i]);
                }
            } else {
                _.setAttribute.call(this, _.makeUuid(), input, silent || false);
            }

            return this;
        },

        remove : function(input, silent) {
            if (typeof input === "function") {
                this.each(function(item, key) {
                    if (input(item)) {
                        _.removeAttribute.call(this, key, silent);
                    }
                });
            } else {
                // nb: checking for exists happens in removeAttribute
                _.removeAttribute.call(this, input, silent || false);
            }

            return this;
        },

        set : function(objOrKey, value, silent) {
            if (typeof objOrKey === "object") {
                for (var key in objOrKey) {
                    _.setAttribute.call(this, key, objOrKey[key]);
                }
            } else {
                _.setAttribute.call(this, objOrKey, value, silent || false);
            }

            return this;
        },

        size : function() {
            return Object.keys(_.attributes[this._guid]).length;
        },

        update : function(keyOrFn, fn, silent) {
            if (typeof keyOrFn === "string") {
                _.updateAttribute.call(this, keyOrFn, fn, silent || false);
            } else if (typeof keyOrFn === "function") {
                this.each(function(value, key) {
                    _.updateAttribute.call(this, key, keyOrFn);
                });
            }

            return this;
        }
    };

    var Stapes = {
        "_" : _, // private helper functions and properties

        "create" : function() {
            return _.createModule( _.Module );
        },

        "extend" : function(obj) {
            return _.extend(_.Module, obj);
        },

        "mixinEvents" : function(obj) {
            obj = obj || {};

            _.addGuid(obj);

            return _.extend(obj, Events);
        },

        "on" : function() {
            _.addEventHandler.apply(this, arguments);
        },

        "version" : VERSION
    };

    // This library can be used as an AMD module, a Node.js module, or an
    // old fashioned global
    if (typeof exports !== "undefined") {
        // Server
        if (typeof module !== "undefined" && module.exports) {
            exports = module.exports = Stapes;
        }
        exports.Stapes = Stapes;
    } else if (typeof define === "function" && define.amd) {
        // AMD
        define(function() {
            return Stapes;
        });
    } else {
        // Global scope
        window.Stapes = Stapes;
    }
})();<commit_msg>forgot silent attribute in method call<commit_after>//
//  ____  _                           _
// / ___|| |_ __ _ _ __   ___  ___   (_)___  (*)
// \___ \| __/ _` | '_ \ / _ \/ __|  | / __|
//  ___) | || (_| | |_) |  __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
//              |_|              |__/
//
// (*) a (really) tiny Javascript MVC microframework
//
// (c) Hay Kranen < hay@bykr.org >
// Released under the terms of the MIT license
// < http://en.wikipedia.org/wiki/MIT_License >
//
// Stapes.js : http://hay.github.com/stapes

(function() {
    'use strict';

    var VERSION = "0.6.1";

    // Global counter for all events in all modules (including mixed in objects)
    var guid = 1;

    // Makes _.create() faster
    var CachedFunction = function(){};

    // Private attributes and helper functions, stored in an object so they
    // are overwritable by plugins
    var _ = {
        // Properties
        attributes : {},

        eventHandlers : {
            "-1" : {} // '-1' is used for the global event handling
        },

        guid : -1,

        // Methods
        addEvent : function(event) {
            // If we don't have any handlers for this type of event, add a new
            // array we can use to push new handlers
            if (!_.eventHandlers[event.guid][event.type]) {
                _.eventHandlers[event.guid][event.type] = [];
            }

            // Push an event object
            _.eventHandlers[event.guid][event.type].push({
                "guid" : event.guid,
                "handler" : event.handler,
                "scope" : event.scope,
                "type" : event.type
            });
        },

        addEventHandler : function(argTypeOrMap, argHandlerOrScope, argScope) {
            var eventMap = {},
                scope;

            if (typeof argTypeOrMap === "string") {
                scope = argScope || false;
                eventMap[ argTypeOrMap ] = argHandlerOrScope;
            } else {
                scope = argHandlerOrScope || false;
                eventMap = argTypeOrMap;
            }

            for (var eventString in eventMap) {
                var handler = eventMap[eventString];
                var events = eventString.split(" ");

                for (var i = 0, l = events.length; i < l; i++) {
                    var eventType = events[i];
                    _.addEvent.call(this, {
                        "guid" : this._guid || this._.guid,
                        "handler" : handler,
                        "scope" : scope,
                        "type" : eventType
                    });
                }
            }
        },

        addGuid : function(object, forceGuid) {
            if (object._guid && !forceGuid) return;

            object._guid = guid++;

            _.attributes[object._guid] = {};
            _.eventHandlers[object._guid] = {};
        },

        // This is a really small utility function to save typing and produce
        // better optimized code
        attr : function(guid) {
            return _.attributes[guid];
        },

        clone : function(obj) {
            return _.extend({}, obj);
        },

        create : function(obj) {
            CachedFunction.prototype = obj;
            return new CachedFunction();
        },

        // Stapes objects have some extra properties that are set on creation
        createModule : function( context ) {
            var instance = _.create( context );

            _.addGuid( instance, true );

            // Mixin events
            Stapes.mixinEvents( instance );

            return instance;
        },

        emitEvents : function(type, data, explicitType, explicitGuid) {
            explicitType = explicitType || false;
            explicitGuid = explicitGuid || this._guid;

            var handlers = _.eventHandlers[explicitGuid][type];

            for (var i = 0, l = handlers.length; i < l; i++) {
                // Clone the event to prevent issue #19
                var event = _.extend({}, handlers[i]);
                var scope = (event.scope) ? event.scope : this;

                if (explicitType) {
                    event.type = explicitType;
                }

                event.scope = scope;
                event.handler.call(event.scope, data, event);
            }
        },

        extend : function(obj, props) {
            for (var key in props) {
                obj[key] = props[key];
            }

            return obj;
        },

        // from http://stackoverflow.com/a/2117523/152809
        makeUuid : function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        },

        removeAttribute : function(keys, silent) {
            silent = silent || false;

            // Split the key, maybe we want to remove more than one item
            var attributes = _.trim(keys).split(" ");

            // Actually delete the item
            for (var i = 0, l = attributes.length; i < l; i++) {
                var key = _.trim(attributes[i]);

                if (key) {
                    delete _.attr(this._guid)[key];

                    // If 'silent' is set, do not throw any events
                    if (!silent) {
                        this.emit('change', key);
                        this.emit('change:' + key);
                        this.emit('remove', key);
                        this.emit('remove:' + key);
                    }
                }
            }
        },

        removeEventHandler : function(type, handler) {
            var handlers = _.eventHandlers[this._guid];

            if (type && handler) {
                // Remove a specific handler
                handlers = handlers[type];
                if (!handlers) return;

                for (var i = 0, l = handlers.length, h; i < l; i++) {
                    h = handlers[i].handler;
                    if (h && h === handler) {
                        handlers.splice(i--, 1);
                        l--;
                    }
                }
            } else if (type) {
                // Remove all handlers for a specific type
                delete handlers[type];
            } else {
                // Remove all handlers for this module
                _.eventHandlers[this._guid] = {};
            }
        },

        setAttribute : function(key, value, silent) {
            silent = silent || false;

            // We need to do this before we actually add the item :)
            var itemExists = this.has(key);
            var oldValue = _.attr(this._guid)[key];

            // Is the value different than the oldValue? If not, ignore this call
            if (value === oldValue) {
                return;
            }

            // Actually add the item to the attributes
            _.attr(this._guid)[key] = value;

            // If 'silent' flag is set, do not throw any events
            if (silent) {
                return;
            }

            // Throw a generic event
            this.emit('change', key);

            // And a namespaced event as well, NOTE that we pass value instead of
            // key here!
            this.emit('change:' + key, value);

            // Throw namespaced and non-namespaced 'mutate' events as well with
            // the old value data as well and some extra metadata such as the key
            var mutateData = {
                "key" : key,
                "newValue" : value,
                "oldValue" : oldValue || null
            };

            this.emit('mutate', mutateData);
            this.emit('mutate:' + key, mutateData);

            // Also throw a specific event for this type of set
            var specificEvent = itemExists ? 'update' : 'create';

            this.emit(specificEvent, key);

            // And a namespaced event as well, NOTE that we pass value instead of key
            this.emit(specificEvent + ':' + key, value);
        },

        trim : function(str) {
            return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        },

        typeOf : function(val) {
            if (val === null || typeof val === "undefined") {
                return String(val);
            } else {
                return Object.prototype.toString.call(val).replace(/\[object |\]/g, '').toLowerCase();
            }
        },

        updateAttribute : function(key, fn, silent) {
            var item = this.get(key);
            var newValue = fn.call(this, _.clone(item), key);
            _.setAttribute.call(this, key, newValue, silent || false);
        }
    };

    // Can be mixed in later using Stapes.mixinEvents(object);
    var Events = {
        emit : function(types, data) {
            data = (typeof data === "undefined") ? null : data;

            var splittedTypes = types.split(" ");

            for (var i = 0, l = splittedTypes.length; i < l; i++) {
                var type = splittedTypes[i];

                // First 'all' type events: is there an 'all' handler in the
                // global stack?
                if (_.eventHandlers[-1].all) {
                    _.emitEvents.call(this, "all", data, type, -1);
                }

                // Catch all events for this type?
                if (_.eventHandlers[-1][type]) {
                    _.emitEvents.call(this, type, data, type, -1);
                }

                if (typeof this._guid === 'number') {
                    // 'all' event for this specific module?
                    if (_.eventHandlers[this._guid].all) {
                        _.emitEvents.call(this, "all", data, type);
                    }

                    // Finally, normal events :)
                    if (_.eventHandlers[this._guid][type]) {
                        _.emitEvents.call(this, type, data);
                    }
                }
            }
        },

        off : function() {
            _.removeEventHandler.apply(this, arguments);
        },

        on : function() {
            _.addEventHandler.apply(this, arguments);
        }
    };

    _.Module = {
        create : function() {
            return _.createModule( this );
        },

        each : function(fn, ctx) {
            var attr = _.attr(this._guid);
            for (var key in attr) {
                var value = attr[key];
                fn.call(ctx || this, value, key);
            }
        },

        extend : function(objectOrValues, valuesIfObject) {
            var object = (valuesIfObject) ? objectOrValues : this;
            var values = (valuesIfObject) ? valuesIfObject : objectOrValues;

            _.extend(object, values);

            return this;
        },

        filter : function(fn) {
            var filtered = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                if ( fn.call(this, attributes[key], key)) {
                    filtered.push( attributes[key] );
                }
            }

            return filtered;
        },

        get : function(input) {
            if (typeof input === "string") {
                return this.has(input) ? _.attr(this._guid)[input] : null;
            } else if (typeof input === "function") {
                var items = this.filter(input);
                return (items.length) ? items[0] : null;
            }
        },

        getAll : function() {
            return _.clone( _.attr(this._guid) );
        },

        getAllAsArray : function() {
            var arr = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                var value = attributes[key];

                if (_.typeOf(value) === "object" && !value.id) {
                    value.id = key;
                }

                arr.push(value);
            }

            return arr;
        },

        has : function(key) {
            return (typeof _.attr(this._guid)[key] !== "undefined");
        },

        // Akin to set(), but makes a unique id
        push : function(input, silent) {
            if (_.typeOf(input) === "array") {
                for (var i = 0, l = input.length; i < l; i++) {
                    _.setAttribute.call(this, _.makeUuid(), input[i]);
                }
            } else {
                _.setAttribute.call(this, _.makeUuid(), input, silent || false);
            }

            return this;
        },

        remove : function(input, silent) {
            if (typeof input === "function") {
                this.each(function(item, key) {
                    if (input(item)) {
                        _.removeAttribute.call(this, key, silent);
                    }
                });
            } else {
                // nb: checking for exists happens in removeAttribute
                _.removeAttribute.call(this, input, silent || false);
            }

            return this;
        },

        set : function(objOrKey, value, silent) {
            if (typeof objOrKey === "object") {
                for (var key in objOrKey) {
                    _.setAttribute.call(this, key, objOrKey[key]);
                }
            } else {
                _.setAttribute.call(this, objOrKey, value, silent || false);
            }

            return this;
        },

        size : function() {
            return Object.keys(_.attributes[this._guid]).length;
        },

        update : function(keyOrFn, fn, silent) {
            if (typeof keyOrFn === "string") {
                _.updateAttribute.call(this, keyOrFn, fn, silent || false);
            } else if (typeof keyOrFn === "function") {
                this.each(function(value, key) {
                    _.updateAttribute.call(this, key, keyOrFn);
                });
            }

            return this;
        }
    };

    var Stapes = {
        "_" : _, // private helper functions and properties

        "create" : function() {
            return _.createModule( _.Module );
        },

        "extend" : function(obj) {
            return _.extend(_.Module, obj);
        },

        "mixinEvents" : function(obj) {
            obj = obj || {};

            _.addGuid(obj);

            return _.extend(obj, Events);
        },

        "on" : function() {
            _.addEventHandler.apply(this, arguments);
        },

        "version" : VERSION
    };

    // This library can be used as an AMD module, a Node.js module, or an
    // old fashioned global
    if (typeof exports !== "undefined") {
        // Server
        if (typeof module !== "undefined" && module.exports) {
            exports = module.exports = Stapes;
        }
        exports.Stapes = Stapes;
    } else if (typeof define === "function" && define.amd) {
        // AMD
        define(function() {
            return Stapes;
        });
    } else {
        // Global scope
        window.Stapes = Stapes;
    }
})();<|endoftext|>


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

(function() {
  var PROMISE_REJECTION_LOGGING_DELAY = 10 * 1000; // 10s
  var logRejectionTimeoutId;
  var unhandledRejections = [];

  function logRejectedPromises() {
    unhandledRejections.forEach(({reason}) =>
        IOWA.Analytics.trackError('UnhandledPromiseRejection', reason));

    unhandledRejections = [];
    logRejectionTimeoutId = null;
  }

  window.addEventListener('unhandledrejection', function(event) {
    debugLog('unhandledrejection fired: ' + event.reason);
    // Keep track of rejected promises by adding them to the list.
    unhandledRejections.push({promise: event.promise, reason: event.reason});

    // We need to wait before we log this rejected promise, since there's a
    // chance it will be caught later on, in which case it's not an error.
    if (!logRejectionTimeoutId) {
      logRejectionTimeoutId = setTimeout(logRejectedPromises,
        PROMISE_REJECTION_LOGGING_DELAY);
    }
  });

  window.addEventListener('rejectionhandled', function(event) {
    debugLog('rejectionhandled fired: ' + event.reason);

    // If a previously rejected promise is handled, remove it from the list.
    unhandledRejections = unhandledRejections.filter(rejection =>
        rejection.promise !== event.promise);
  });

  function initWorker() {
    var MAX_WORKER_TIMEOUT_ = 10 * 1000; // 10s
    var worker;

    var doMetrics = window.performance && window.performance.now;

    if (doMetrics) {
      var workerStartTime = window.performance.now();
      worker = new Worker('data-worker-scripts.js');
      var total = window.performance.now() - workerStartTime;

      debugLog('worker startup:', total, 'ms');
      IOWA.Analytics.trackPerf('worker', 'creation', Math.ceil(total),
                               null, MAX_WORKER_TIMEOUT_);
    } else {
      worker = new Worker('data-worker-scripts.js');
    }

    var workerFetchTime;
    if (doMetrics) {
      workerFetchTime = window.performance.now();
    }

    worker.addEventListener('message', function(e) {
      if (!e.data) {
        return;
      }

      var data = e.data;
      if (data.scheduleData) {
        // Report how long the worker fetch took to GA.
        if (doMetrics) {
          var total = window.performance.now() - workerFetchTime;
          debugLog('worker fetch:', total, 'ms');
          IOWA.Analytics.trackPerf('worker', 'data fetch', Math.ceil(total),
                                   null, MAX_WORKER_TIMEOUT_);
        }

        IOWA.Schedule.resolveSchedulePromise(data);
      }
    });

    worker.postMessage({cmd: 'FETCH_SCHEDULE'});
  }

  function lazyLoadWCPolyfillsIfNecessary(callback = null) {
    const onload = function() {
      // For native Imports, manually fire WCR so user code
      // can use the same code path for native and polyfill'd imports.
      if (!window.HTMLImports) {
        document.dispatchEvent(
            new CustomEvent('WebComponentsReady', {bubbles: true}));
      }
      if (callback) {
        callback();
      }
    };

    var webComponentsSupported = (
      'registerElement' in document &&
      'import' in document.createElement('link') &&
      'content' in document.createElement('template'));
    if (webComponentsSupported) {
      onload();
    } else {
      var script = document.createElement('script');
      script.async = true;
      script.src = 'bower_components/webcomponentsjs/webcomponents-lite.min.js';
      script.onload = onload;
      document.head.appendChild(script);
    }
  }

  function removeSplashScreen() {
    var splash = document.getElementById('splash');
    splash.addEventListener('transitionend', function() {
      splash.parentElement.removeChild(splash);
    });

    document.body.classList.remove('loading');
  }

  function afterCriticalImports() {
    Polymer.Base.importHref('/io2016/elements/elements.html', function() {
      IOWA.Elements.onElementsBundleLoaded();

      // Note: this is also done in <io-notifications-widget>'s ready,
      // but we need this code to run before that element is attached.
      if (IOWA.Notifications && !IOWA.Notifications.supported) {
        document.body.classList.add('nosupport-notifications');
      }

      removeSplashScreen();

      var fp = IOWA.Util.getFPIfSupported();
      if (fp) {
        debugLog('first paint:', fp, 'ms');
        IOWA.Analytics.trackPerf(
            'load', 'firstpaint', fp, null, IOWA.Analytics.FP_TIMEOUT_);
      }
    }, null, true);

    IOWA.Schedule.loadCachedUserSchedule();
  }

  window.addEventListener('keydown', function(e) {
    // ESC closes any overlays.
    if (e.keyCode === 27) {
      var subnav = document.querySelector('io-schedule-subnav');
      if (subnav) {
        subnav.closeFilters();
      }

      var live = document.querySelector('io-live');
      if (live) {
        live.closeFab();
      }
    }
  });

  window.addEventListener('resize', function() {
    // FF mobile sends resize event on page load. Be careful!
    if (IOWA.Elements && IOWA.Elements.Template &&
        IOWA.Elements.Template.debounce) {
      IOWA.Elements.Template.debounce('resize', function() {
        this.closeDrawer();
        // remove fab sticky scrolling behavior for mobile. Add for desktop.
        this.initFabScroll();
      }, 400);
    }
  });

  window.addEventListener('offline', function() {
    if (IOWA.Elements && IOWA.Elements.Toast) {
      IOWA.Elements.Toast.showMessage(
        'Offline. Changes you make to My Schedule will be saved for later.');
    }
  });

  // See https://developers.google.com/web/fundamentals/engage-and-retain/app-install-banners/advanced
  window.addEventListener('beforeinstallprompt', function(event) {
    IOWA.Analytics.trackEvent('installprompt', 'fired');

    event.userChoice.then(function(choiceResult) {
      // choiceResult.outcome will be 'accepted' or 'dismissed'.
      // choiceResult.platform will be 'web' or 'android' if the prompt was
      // accepted, or '' if the prompt was dismissed.
      IOWA.Analytics.trackEvent('installprompt', choiceResult.outcome,
        choiceResult.platform);
    });
  });

  function initApp() {
    initWorker(); // Kick off fetching master schedule asap.

    // wc.js brings in a URL() polyfill that we need to wait for in unsupported
    // browsers. In Chrome, lazyLoadWCPolyfillsIfNecessary is effectively not
    // async. It's a noop and its callback gets invoked right away. Therefore,
    // this shouldn't slow anything down.
    lazyLoadWCPolyfillsIfNecessary(() => {
      IOWA.Router = IOWA.Router_(window); // eslint-disable-line new-cap
      IOWA.Elements.init();
      IOWA.Router.init(IOWA.Elements.Template);
    });

    // Wait for critical.html to load if we don't have native HTML imports.
    // Can't use Polymer.RenderStatus.whenReady() b/c potentially, we have
    // to wait for the polyfills to load (above) and the critical.html to
    // load so Polymer is defined. Instead, wait for HTMLImportsLoaded if
    // we're in a polyfilled browser (but go right away if Imports are native).
    if (IOWA.Util.supportsHTMLImports) {
      afterCriticalImports();
    } else {
      document.addEventListener('HTMLImportsLoaded', afterCriticalImports);
    }
  }

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

(function() {
  var PROMISE_REJECTION_LOGGING_DELAY = 10 * 1000; // 10s
  var logRejectionTimeoutId;
  var unhandledRejections = [];

  function logRejectedPromises() {
    unhandledRejections.forEach(({reason}) =>
        IOWA.Analytics.trackError('UnhandledPromiseRejection', reason));

    unhandledRejections = [];
    logRejectionTimeoutId = null;
  }

  window.addEventListener('unhandledrejection', function(event) {
    debugLog('unhandledrejection fired: ' + event.reason);
    // Keep track of rejected promises by adding them to the list.
    unhandledRejections.push({promise: event.promise, reason: event.reason});

    // We need to wait before we log this rejected promise, since there's a
    // chance it will be caught later on, in which case it's not an error.
    if (!logRejectionTimeoutId) {
      logRejectionTimeoutId = setTimeout(logRejectedPromises,
        PROMISE_REJECTION_LOGGING_DELAY);
    }
  });

  window.addEventListener('rejectionhandled', function(event) {
    debugLog('rejectionhandled fired: ' + event.reason);

    // If a previously rejected promise is handled, remove it from the list.
    unhandledRejections = unhandledRejections.filter(rejection =>
        rejection.promise !== event.promise);
  });

  function initWorker() {
    if (!('Worker' in window)) {
      return;
    }

    var MAX_WORKER_TIMEOUT_ = 10 * 1000; // 10s
    var worker;

    var doMetrics = window.performance && window.performance.now;

    if (doMetrics) {
      var workerStartTime = window.performance.now();
      worker = new Worker('data-worker-scripts.js');
      var total = window.performance.now() - workerStartTime;

      debugLog('worker startup:', total, 'ms');
      IOWA.Analytics.trackPerf('worker', 'creation', Math.ceil(total),
                               null, MAX_WORKER_TIMEOUT_);
    } else {
      worker = new Worker('data-worker-scripts.js');
    }

    var workerFetchTime;
    if (doMetrics) {
      workerFetchTime = window.performance.now();
    }

    worker.addEventListener('message', function(e) {
      if (!e.data) {
        return;
      }

      var data = e.data;
      if (data.scheduleData) {
        // Report how long the worker fetch took to GA.
        if (doMetrics) {
          var total = window.performance.now() - workerFetchTime;
          debugLog('worker fetch:', total, 'ms');
          IOWA.Analytics.trackPerf('worker', 'data fetch', Math.ceil(total),
                                   null, MAX_WORKER_TIMEOUT_);
        }

        IOWA.Schedule.resolveSchedulePromise(data);
      }
    });

    worker.postMessage({cmd: 'FETCH_SCHEDULE'});
  }

  function lazyLoadWCPolyfillsIfNecessary(callback = null) {
    const onload = function() {
      // For native Imports, manually fire WCR so user code
      // can use the same code path for native and polyfill'd imports.
      if (!window.HTMLImports) {
        document.dispatchEvent(
            new CustomEvent('WebComponentsReady', {bubbles: true}));
      }
      if (callback) {
        callback();
      }
    };

    var webComponentsSupported = (
      'registerElement' in document &&
      'import' in document.createElement('link') &&
      'content' in document.createElement('template'));
    if (webComponentsSupported) {
      onload();
    } else {
      var script = document.createElement('script');
      script.async = true;
      script.src = 'bower_components/webcomponentsjs/webcomponents-lite.min.js';
      script.onload = onload;
      document.head.appendChild(script);
    }
  }

  function removeSplashScreen() {
    var splash = document.getElementById('splash');
    splash.addEventListener('transitionend', function() {
      splash.parentElement.removeChild(splash);
    });

    document.body.classList.remove('loading');
  }

  function afterCriticalImports() {
    Polymer.Base.importHref('/io2016/elements/elements.html', function() {
      IOWA.Elements.onElementsBundleLoaded();

      // Note: this is also done in <io-notifications-widget>'s ready,
      // but we need this code to run before that element is attached.
      if (IOWA.Notifications && !IOWA.Notifications.supported) {
        document.body.classList.add('nosupport-notifications');
      }

      removeSplashScreen();

      var fp = IOWA.Util.getFPIfSupported();
      if (fp) {
        debugLog('first paint:', fp, 'ms');
        IOWA.Analytics.trackPerf(
            'load', 'firstpaint', fp, null, IOWA.Analytics.FP_TIMEOUT_);
      }
    }, null, true);

    IOWA.Schedule.loadCachedUserSchedule();
  }

  window.addEventListener('keydown', function(e) {
    // ESC closes any overlays.
    if (e.keyCode === 27) {
      var subnav = document.querySelector('io-schedule-subnav');
      if (subnav) {
        subnav.closeFilters();
      }

      var live = document.querySelector('io-live');
      if (live) {
        live.closeFab();
      }
    }
  });

  window.addEventListener('resize', function() {
    // FF mobile sends resize event on page load. Be careful!
    if (IOWA.Elements && IOWA.Elements.Template &&
        IOWA.Elements.Template.debounce) {
      IOWA.Elements.Template.debounce('resize', function() {
        this.closeDrawer();
        // remove fab sticky scrolling behavior for mobile. Add for desktop.
        this.initFabScroll();
      }, 400);
    }
  });

  window.addEventListener('offline', function() {
    if (IOWA.Elements && IOWA.Elements.Toast) {
      IOWA.Elements.Toast.showMessage(
        'Offline. Changes you make to My Schedule will be saved for later.');
    }
  });

  // See https://developers.google.com/web/fundamentals/engage-and-retain/app-install-banners/advanced
  window.addEventListener('beforeinstallprompt', function(event) {
    IOWA.Analytics.trackEvent('installprompt', 'fired');

    event.userChoice.then(function(choiceResult) {
      // choiceResult.outcome will be 'accepted' or 'dismissed'.
      // choiceResult.platform will be 'web' or 'android' if the prompt was
      // accepted, or '' if the prompt was dismissed.
      IOWA.Analytics.trackEvent('installprompt', choiceResult.outcome,
        choiceResult.platform);
    });
  });

  function initApp() {
    initWorker(); // Kick off fetching master schedule asap.

    // wc.js brings in a URL() polyfill that we need to wait for in unsupported
    // browsers. In Chrome, lazyLoadWCPolyfillsIfNecessary is effectively not
    // async. It's a noop and its callback gets invoked right away. Therefore,
    // this shouldn't slow anything down.
    lazyLoadWCPolyfillsIfNecessary(() => {
      IOWA.Router = IOWA.Router_(window); // eslint-disable-line new-cap
      IOWA.Elements.init();
      IOWA.Router.init(IOWA.Elements.Template);
    });

    // Wait for critical.html to load if we don't have native HTML imports.
    // Can't use Polymer.RenderStatus.whenReady() b/c potentially, we have
    // to wait for the polyfills to load (above) and the critical.html to
    // load so Polymer is defined. Instead, wait for HTMLImportsLoaded if
    // we're in a polyfilled browser (but go right away if Imports are native).
    if (IOWA.Util.supportsHTMLImports) {
      afterCriticalImports();
    } else {
      document.addEventListener('HTMLImportsLoaded', afterCriticalImports);
    }
  }

  initApp();
})();
<|endoftext|>


<commit_before>'use strict';

angular.module('repicbro.constants')
  .constant('constants', {
    'apiPrefix' : 'http://www.reddit.com/r/',
    'blacklist': [
      /imgur\.com\/gallery/,
      /imgur\.com\/a\//,
      /www\.reddit\.com/
    ],
    'subreddits': [
      'funny',
      'pics',
      'gifs',
      'aww',
      'earthporn',
      'adviceanimals',
      'minecraft'
    ]
  });
<commit_msg>Adds flickr to blacklist<commit_after>'use strict';

angular.module('repicbro.constants')
  .constant('constants', {
    'apiPrefix' : 'http://www.reddit.com/r/',
    'blacklist': [
      /imgur\.com\/gallery/,
      /imgur\.com\/a\//,
      /www\.reddit\.com/,
      /www\.flickr\.com/
    ],
    'subreddits': [
      'funny',
      'pics',
      'gifs',
      'aww',
      'earthporn',
      'adviceanimals',
      'minecraft'
    ]
  });
<|endoftext|>


<commit_before>'use strict';

angular.module('Grid', [])
.factory('TileModel', function() {
    var Tile = function(coordinate, val) {
        this.coordinate = coordinate;
        this.value = val || 2;
        this.merged = false;
    };

    Tile.prototype.getPosition = function() {
        return this.coordinate;
    };

    Tile.prototype.updateCoordinate = function(newCoordinate) {
        this.coordinate = newCoordinate;
    };

    // Reset tile to initial state.
    Tile.prototype.reset = function() {
        this.merged = false;
    };

    return Tile;
})
.service('GridManager', ['TileModel', function(TileModel) {
        this.grid = [];
        this.tiles = [];
        // Size of the game board
        this.size = 4;
        // Initial Number of tiles
        this.initTileNumber = 2;

        var directions = {
            'left': { x: -1, y: 0 },
            'right': { x: 1, y: 0 },
            'up': { x: 0, y: -1 },
            'down': { x: 0, y: 1 }
        };

        // Helper function for create an new tile
        this.newTile = function(coordinate, val) {
            return new TileModel(coordinate, val);
        };

        // Generate empty game board with null for each cell
        this.generateEmptyGameBoard = function() {
            for (var i = 0; i < Math.pow(this.size, 2); i++) {
                this.grid[i] = null;
                this.tiles[i] = null;
            }
        };

        // Initialize game board (randomly insert initTileNumber of tiles
        this.initGameBoard = function() {
            for (var i = 0; i < this.initTileNumber; i++) {
                this.randomlyInsertTile();
            }
        };

        // Check if there are any matches available
        this.isTileMatchesAvailable = function() {
            for (var i = 0; i < Math.pow(this.size, 2); i++) {
                var coordinate = this._positionToCoordinate(i);
                var tile = this.tiles[i];

                if(tile) {
                    // Check all directions
                    for (var direction in directions) {
                        var direct = directions[direction];
                        var cell = { x: coordinate.x + direct.x, y: coordinate.y + direct.y };
                        if (this.isWithinGrid(cell) && this.getCellAt(cell).value == tile.value) {
                            return true;
                        }
                    }
                }
            }

            return false;
        };

        // Return ordered coordinates in direction
        this.coordinatesInDirection = function(key) {
            var direction = directions[key];
            var coordinates = {x: [], y: []};
            for (var i = 0; i < this.size; i++) {
                coordinates.x.push(i);
                coordinates.y.push(i);
            }
            // Going right
            if (direction.x > 0) {
                coordinates.x = coordinates.x.reverse();
            }
            // Going down
            if (direction.y > 0) {
                coordinates.y = coordinates.y.reverse();
            }
            return coordinates;
        };

        // Return the next possible coordinate for a tile to move
        this.nextAvailableCellInDirection = function(coordinate, key) {
            var direction = directions[key];
            var previous;

            do {
                previous = coordinate;
                coordinate = {
                    x: previous.x + direction.x,
                    y: previous.y + direction.y
                };
            } while (this.isCellAvailable(coordinate));

            return {
                nextCoordinate: previous,
                nextTile: this.getCellAt(coordinate)
            }
        };

        // Check to see there is a cell available at a given coordinate
        this.isCellAvailable = function(coordinate) {
            return this.isWithinGrid(coordinate) && !this.getCellAt(coordinate);
        };

        // Return coordinates of cells that don't contain a tile
        this.availableCells = function() {
            var cells = [];
            var self = this;
            this.forEachCell(function(coordinate) {
                if (self.isCellAvailable(coordinate)) {
                    cells.push(coordinate);
                }
            });

            return cells;
        };

        // Check to see there are still cells available
        this.isAnyCellAvailable = function() {
            return this.availableCells().length > 0;
        };

        // Return a random selected available cell
        this.randomAvailableCell = function() {
            var cells = this.availableCells();
            if (cells.length > 1) {
                return cells[Math.floor(Math.random() * cells.length)];
            }
        };

        // Apply callback function on each cell
        this.forEachCell = function(cb) {
            for (var i = 0; i < Math.pow(this.size, 2); i++) {
                var coordinate = this._positionToCoordinate(i);
                cb(coordinate, this.tiles[i]);
            }
        };

        // Set cell object by providing coordinate
        this.setCellAt = function(coordinate, tile) {
            var pos = this._coordinateToPosition(coordinate);
            this.tiles[pos] = tile;
        };

        // Get cell object by providing coordinate
        this.getCellAt = function(coordinate) {
            var pos = this._coordinateToPosition(coordinate);
            return this.tiles[pos];
        };

        // Prepare tiles for move
        this.prepareTiles = function() {
            this.forEachCell(function(coordinate, tile) {
                if (tile) {
                    tile.reset();
                }
            });
        };

        // Insert a tile to a cell
        this.insertTile = function(tile) {
            this.setCellAt(tile.coordinate, tile);
        };

        // Remove a current tile by delete it
        this.removeTile = function(tile) {
            var pos = this._coordinateToPosition(tile.coordinate);
            delete this.tiles[pos];
        };

        // Insert a new tile to a random available cell
        this.randomlyInsertTile = function() {
            var cell = this.randomAvailableCell();
            var tile = this.newTile(cell, 2);
            this.setCellAt(tile.coordinate, tile);
        };

        // Move a tile to new coordinate
        this.moveTile = function(tile, newCoordinate) {
            if (this.isWithinGrid(newCoordinate)) {
                this.setCellAt(tile.coordinate, null);
                this.setCellAt(newCoordinate, tile);
                tile.updateCoordinate(newCoordinate);
            }
        };

        // Convert position into actual coordinate
        this._positionToCoordinate = function(i) {
            if (i < 0 || i >= Math.pow(this.size, 2)) {
                return null;
            }
            return {x: Math.trunc(i / this.size), y: i % this.size};
        };

        // Convert coordinate into position
        this._coordinateToPosition = function(coordinate) {
            return coordinate.x * this.size + coordinate.y;
        };

        // Check whether a given coordinate is outside the grid
        this.isWithinGrid = function(coordinate) {
            return coordinate.x >= 0 && coordinate.x < this.size && coordinate.y >= 0 && coordinate.y < this.size
        };

        // Check if two coordinates are the same
        this.areSameCoordinates = function(a, b) {
            return a.x === b.x ? a.y === b.y : false;
        }
    }]);<commit_msg>Add id for each tile to be used in directive.<commit_after>'use strict';

angular.module('Grid', [])
.factory('TileModel', function() {
    var id = 16; // skip index

    var Tile = function(coordinate, val) {
        this.coordinate = coordinate;
        this.value = val || 2;
        this.merged = false;
        this.id = id++;
    };

    Tile.prototype.getPosition = function() {
        return this.coordinate;
    };

    Tile.prototype.updateCoordinate = function(newCoordinate) {
        this.coordinate = newCoordinate;
    };

    // Reset tile to initial state.
    Tile.prototype.reset = function() {
        this.merged = false;
    };

    return Tile;
})
.service('GridManager', ['TileModel', function(TileModel) {
        this.grid = [];
        this.tiles = [];
        // Size of the game board
        this.size = 4;
        // Initial Number of tiles
        this.initTileNumber = 2;

        var directions = {
            'left': { x: -1, y: 0 },
            'right': { x: 1, y: 0 },
            'up': { x: 0, y: -1 },
            'down': { x: 0, y: 1 }
        };

        // Helper function for create an new tile
        this.newTile = function(coordinate, val) {
            return new TileModel(coordinate, val);
        };

        // Generate empty game board with null for each cell
        this.generateEmptyGameBoard = function() {
            for (var i = 0; i < Math.pow(this.size, 2); i++) {
                this.grid[i] = null;
                this.tiles[i] = null;
            }
        };

        // Initialize game board (randomly insert initTileNumber of tiles
        this.initGameBoard = function() {
            for (var i = 0; i < this.initTileNumber; i++) {
                this.randomlyInsertTile();
            }
        };

        // Check if there are any matches available
        this.isTileMatchesAvailable = function() {
            for (var i = 0; i < Math.pow(this.size, 2); i++) {
                var coordinate = this._positionToCoordinate(i);
                var tile = this.tiles[i];

                if(tile) {
                    // Check all directions
                    for (var direction in directions) {
                        var direct = directions[direction];
                        var cell = { x: coordinate.x + direct.x, y: coordinate.y + direct.y };
                        if (this.isWithinGrid(cell) && this.getCellAt(cell).value == tile.value) {
                            return true;
                        }
                    }
                }
            }

            return false;
        };

        // Return ordered coordinates in direction
        this.coordinatesInDirection = function(key) {
            var direction = directions[key];
            var coordinates = {x: [], y: []};
            for (var i = 0; i < this.size; i++) {
                coordinates.x.push(i);
                coordinates.y.push(i);
            }
            // Going right
            if (direction.x > 0) {
                coordinates.x = coordinates.x.reverse();
            }
            // Going down
            if (direction.y > 0) {
                coordinates.y = coordinates.y.reverse();
            }
            return coordinates;
        };

        // Return the next possible coordinate for a tile to move
        this.nextAvailableCellInDirection = function(coordinate, key) {
            var direction = directions[key];
            var previous;

            do {
                previous = coordinate;
                coordinate = {
                    x: previous.x + direction.x,
                    y: previous.y + direction.y
                };
            } while (this.isCellAvailable(coordinate));

            return {
                nextCoordinate: previous,
                nextTile: this.getCellAt(coordinate)
            }
        };

        // Check to see there is a cell available at a given coordinate
        this.isCellAvailable = function(coordinate) {
            return this.isWithinGrid(coordinate) && !this.getCellAt(coordinate);
        };

        // Return coordinates of cells that don't contain a tile
        this.availableCells = function() {
            var cells = [];
            var self = this;
            this.forEachCell(function(coordinate) {
                if (self.isCellAvailable(coordinate)) {
                    cells.push(coordinate);
                }
            });

            return cells;
        };

        // Check to see there are still cells available
        this.isAnyCellAvailable = function() {
            return this.availableCells().length > 0;
        };

        // Return a random selected available cell
        this.randomAvailableCell = function() {
            var cells = this.availableCells();
            if (cells.length > 1) {
                return cells[Math.floor(Math.random() * cells.length)];
            }
        };

        // Apply callback function on each cell
        this.forEachCell = function(cb) {
            for (var i = 0; i < Math.pow(this.size, 2); i++) {
                var coordinate = this._positionToCoordinate(i);
                cb(coordinate, this.tiles[i]);
            }
        };

        // Set cell object by providing coordinate
        this.setCellAt = function(coordinate, tile) {
            var pos = this._coordinateToPosition(coordinate);
            this.tiles[pos] = tile;
        };

        // Get cell object by providing coordinate
        this.getCellAt = function(coordinate) {
            var pos = this._coordinateToPosition(coordinate);
            return this.tiles[pos];
        };

        // Prepare tiles for move
        this.prepareTiles = function() {
            this.forEachCell(function(coordinate, tile) {
                if (tile) {
                    tile.reset();
                }
            });
        };

        // Insert a tile to a cell
        this.insertTile = function(tile) {
            this.setCellAt(tile.coordinate, tile);
        };

        // Remove a current tile by delete it
        this.removeTile = function(tile) {
            var pos = this._coordinateToPosition(tile.coordinate);
            delete this.tiles[pos];
        };

        // Insert a new tile to a random available cell
        this.randomlyInsertTile = function() {
            var cell = this.randomAvailableCell();
            var tile = this.newTile(cell, 2);
            this.setCellAt(tile.coordinate, tile);
        };

        // Move a tile to new coordinate
        this.moveTile = function(tile, newCoordinate) {
            if (this.isWithinGrid(newCoordinate)) {
                this.setCellAt(tile.coordinate, null);
                this.setCellAt(newCoordinate, tile);
                tile.updateCoordinate(newCoordinate);
            }
        };

        // Convert position into actual coordinate
        this._positionToCoordinate = function(i) {
            if (i < 0 || i >= Math.pow(this.size, 2)) {
                return null;
            }
            return {x: Math.trunc(i / this.size), y: i % this.size};
        };

        // Convert coordinate into position
        this._coordinateToPosition = function(coordinate) {
            return coordinate.x * this.size + coordinate.y;
        };

        // Check whether a given coordinate is outside the grid
        this.isWithinGrid = function(coordinate) {
            return coordinate.x >= 0 && coordinate.x < this.size && coordinate.y >= 0 && coordinate.y < this.size
        };

        // Check if two coordinates are the same
        this.areSameCoordinates = function(a, b) {
            return a.x === b.x ? a.y === b.y : false;
        }
    }]);<|endoftext|>


<commit_before>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import { saveAs } from 'file-saver';
import uuidv1 from 'uuid';
import PlatformIO from './platform-io';
import AppConfig from '../config';
import {
  extractTagsAsObjects,
  extractFileExtension,
  cleanTrailingDirSeparator,
  getMetaDirectoryPath,
  getMetaFileLocationForFile,
  getMetaFileLocationForDir
} from '../utils/paths';
// import { formatDateTime4Tag } from '../utils/misc';
import versionMeta from '../version.json';
import { type Tag } from '../reducers/taglibrary';

export type FileSystemEntry = {
  uuid: string,
  name: string,
  isFile: boolean,
  extension: string,
  thumbPath?: string,
  textContent?: string,
  description: string,
  tags: Array<Tag>,
  size: number,
  lmdt: number,
  path: string
};

export type FileSystemEntryMeta = {
  description: string,
  tags: Array<Tag>,
  appVersionCreated: string,
  appName: string,
  appVersionUpdated: string,
  lastUpdated: string
};

export function enhanceEntry(entry: any): FileSystemEntry {
  let fileNameTags = [];
  if (entry.isFile) {
    fileNameTags = extractTagsAsObjects(entry.name);
  }
  let sidecarDescription = '';
  let uuid;
  let sidecarTags = [];
  if (entry.meta) {
    sidecarDescription = entry.meta.description || '';
    sidecarTags = entry.meta.tags || [];
    sidecarTags.map((tag) => { tag.type = 'sidecar'; return true; });
    uuid = entry.meta.uuid || undefined;
  }
  const enhancedEntry = {
    uuid: uuid || uuidv1(),
    name: entry.name,
    isFile: entry.isFile,
    extension: entry.isFile ? extractFileExtension(entry.name) : '',
    thumbPath: entry.thumbPath,
    description: sidecarDescription,
    textContent: entry.textContent,
    tags: [...sidecarTags, ...fileNameTags],
    size: entry.size,
    lmdt: entry.lmdt,
    path: entry.path
  };
  // console.log('Enhancing ' + entry.path); console.log(enhancedEntry);
  return enhancedEntry;
}

export function createDirectoryIndex(directoryPath: string, extractText: boolean = false): Promise<Array<Object>> {
  const dirPath = cleanTrailingDirSeparator(directoryPath);
  if (PlatformIO.isWorkerAvailable() && !PlatformIO.haveObjectStoreSupport()) { // Start indexing in worker if not in the object store mode
    return PlatformIO.createDirectoryIndexInWorker(dirPath, extractText);
  }

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const directoryIndex = [];
      let counter = 0;
      console.time('createDirectoryIndex');
      walkDirectory(dirPath, { recursive: true, skipMetaFolder: true, extractText }, (fileEntry) => {
        counter += 1;
        if (counter > AppConfig.indexerLimit) {
          console.warn('Walk canceled by ' + AppConfig.indexerLimit);
          window.walkCanceled = true;
        }
        directoryIndex.push(enhanceEntry(fileEntry));
      }, (directoryEntry) => {
        if (directoryEntry.name !== AppConfig.metaFolder) {
          counter += 1;
          directoryIndex.push(enhanceEntry(directoryEntry));
        }
      }).then(() => { // entries - can be used for further processing
        window.walkCanceled = false;
        console.log('Directory index created ' + dirPath + ' containing ' + directoryIndex.length);
        console.timeEnd('createDirectoryIndex');
        resolve(directoryIndex);
        return true;
      }).catch((err) => {
        window.walkCanceled = false;
        console.timeEnd('createDirectoryIndex');
        console.warn('Error creating index: ' + err);
        reject(err);
      });
    }, 2000);
  });
}

export function walkDirectory(
  path: string,
  options: Object = {},
  fileCallback: any,
  dirCallback: any
) {
  const mergedOptions = {
    recursive: false,
    skipMetaFolder: true,
    loadMetaDate: true,
    extractText: false,
    ...options
  };
  return PlatformIO.listDirectoryPromise(path, false, mergedOptions.extractText).then((entries) => {
    if (window.walkCanceled) {
      return false;
    }
    return Promise.all(entries.map((entry) => {
      if (window.walkCanceled) {
        return false;
      }

      if (entry.isFile) {
        if (fileCallback) {
          fileCallback(entry);
        }
        return entry;
      }

      if (dirCallback) {
        dirCallback(entry);
      }

      if (mergedOptions.recursive) {
        if (mergedOptions.skipMetaFolder && entry.name === AppConfig.metaFolder) {
          return entry;
        }
        return walkDirectory(entry.path, mergedOptions, fileCallback, dirCallback);
      }
      return entry;
    }));
  }).catch((err) => {
    console.warn('Error walking directory ' + err);
    return err;
  });
}

export async function getAllPropertiesPromise(entryPath: string): Promise<FileSystemEntry> {
  const entryProps = await PlatformIO.getPropertiesPromise(entryPath);
  let metaFilePath;
  if (entryProps.isFile) {
    metaFilePath = getMetaFileLocationForFile(entryPath);
  } else {
    metaFilePath = getMetaFileLocationForDir(entryPath);
  }
  const metaFileProps = await PlatformIO.getPropertiesPromise(metaFilePath);
  if (metaFileProps.isFile) {
    entryProps.meta = await loadJSONFile(metaFilePath);
  }
  return enhanceEntry(entryProps);
}

export async function loadJSONFile(filePath: string): any {
  let jsonObject;
  let jsonContent = await PlatformIO.loadTextFilePromise(filePath);
  const UTF8_BOM = '\ufeff';
  if (jsonContent.indexOf(UTF8_BOM) === 0) {
    jsonContent = jsonContent.substring(1, jsonContent.length);
  }
  try {
    jsonObject = JSON.parse(jsonContent);
  } catch (err) {
    console.warn('Error parsing meta json file for ' + filePath + ' - ' + err);
  }
  return jsonObject;
}

export function saveAsTextFile(blob: any, filename: string) {
  saveAs(blob, filename);
}

export function openLinkExternally(uri: string) {
  const decodedURI = decodeURIComponent(uri);
  if (
    decodedURI.startsWith('http://') ||
    decodedURI.startsWith('https://') ||
    decodedURI.startsWith('file://')
  ) {
    PlatformIO.openFile(decodedURI);
  } else {
    console.log('Not supported URL format: ' + decodedURI);
  }
}

export function deleteFilesPromise(filePathList: Array<string>) {
  const fileDeletionPromises = [];
  filePathList.forEach((filePath) => {
    fileDeletionPromises.push(PlatformIO.deleteFilePromise(filePath));
  });
  return Promise.all(fileDeletionPromises);
}

export function renameFilesPromise(renameJobs: Array<Array<string>>) {
  const fileRenamePromises = [];
  renameJobs.forEach((renameJob) => {
    fileRenamePromises.push(PlatformIO.renameFilePromise(renameJob[0], renameJob[1]));
  });
  return Promise.all(fileRenamePromises);
}

export function copyFilesPromise(copyJobs: Array<Array<string>>) {
  const ioJobPromises = [];
  copyJobs.forEach((copyJob) => {
    ioJobPromises.push(PlatformIO.copyFilePromise(copyJob[0], copyJob[1]));
  });
  return Promise.all(ioJobPromises);
}

export async function loadSubFolders(path: string, loadHidden: boolean = false) {
  const folderContent = await PlatformIO.listDirectoryPromise(path, true);
  const subfolders = [];
  let i = 0;
  let isHidden = false;
  folderContent.map((entry) => {
    if (!entry.isFile) {
      isHidden = entry.name.startsWith('.');
      if (isHidden) {
        if (loadHidden) {
          subfolders.push({
            key: '0-' + (i += 1),
            isLeaf: false,
            name: entry.name,
            isFile: entry.isFile,
            lmdt: entry.lmdt,
            meta: entry.meta,
            path: entry.path,
            tags: entry.tags
          });
        } else {
          // do nothing
        }
      } else {
        subfolders.push({
          key: '0-' + (i += 1),
          isLeaf: false,
          name: entry.name,
          isFile: entry.isFile,
          lmdt: entry.lmdt,
          meta: entry.meta,
          path: entry.path,
          tags: entry.tags
        });
      }
    }
    return true;
  });
  return subfolders;
}

export function generateFileName(fileName: string, tags: Array<string>, tagDelimiter: string) {
  let tagsString = '';
  const prefixTagContainer = AppConfig.prefixTagContainer;
  // Creating the string will all the tags by more that 0 tags
  if (tags && tags.length > 0) {
    tagsString = AppConfig.beginTagContainer;
    for (let i = 0; i < tags.length; i += 1) {
      if (i === tags.length - 1) {
        tagsString += tags[i].trim();
      } else {
        tagsString += tags[i].trim() + tagDelimiter;
      }
    }
    tagsString = tagsString.trim() + AppConfig.endTagContainer;
  }
  // console.log('The tags string: ' + tagsString);
  const fileExt = extractFileExtension(fileName);
  // console.log('Filename: ' + fileName + ' file extension: ' + fileExt);
  // Assembling the new filename with the tags
  let newFileName = '';
  const beginTagContainer = fileName.indexOf(AppConfig.beginTagContainer);
  const endTagContainer = fileName.indexOf(AppConfig.endTagContainer);
  const lastDotPosition = fileName.lastIndexOf('.');
  if (beginTagContainer < 0 || endTagContainer < 0 || beginTagContainer >= endTagContainer) {
    // Filename does not contains tags.
    if (lastDotPosition < 0) {
      // File does not have an extension
      newFileName = fileName.trim() + tagsString;
    } else {
      // File has an extension
      newFileName = fileName.substring(0, lastDotPosition).trim() + prefixTagContainer + tagsString + '.' + fileExt;
    }
  } else {
    // File does not have an extension
    newFileName = fileName.substring(0, beginTagContainer).trim() + prefixTagContainer + tagsString + fileName.substring(endTagContainer + 1, fileName.length).trim();
  }
  if (newFileName.length < 1) {
    throw 'Generated filename is invalid';
  }
  // Removing double prefix
  newFileName = newFileName.split(prefixTagContainer + '' + prefixTagContainer).join(prefixTagContainer);
  return newFileName;
}

export async function loadMetaDataPromise(path: string): Promise<FileSystemEntryMeta> {
  const entryProperties = await PlatformIO.getPropertiesPromise(path);
  let metaDataObject;
  if (entryProperties.isFile) {
    const metaFilePath = getMetaFileLocationForFile(path);
    const metaData = await loadJSONFile(metaFilePath);
    metaDataObject = {
      description: metaData.description || '',
      tags: metaData.tags || [],
      appVersionCreated: metaData.appVersionCreated || '',
      appName: metaData.appName || '',
      appVersionUpdated: metaData.appVersionUpdated || '',
      lastUpdated: metaData.lastUpdated || ''
    };
  } else {
    const metaFilePath = getMetaFileLocationForDir(path);
    const metaData = await loadJSONFile(metaFilePath);
    metaDataObject = {
      description: metaData.description || '',
      tags: metaData.tags || [],
      appVersionCreated: metaData.appVersionCreated || '',
      appName: metaData.appName || '',
      appVersionUpdated: metaData.appVersionUpdated || '',
      lastUpdated: metaData.lastUpdated || ''
    };
  }
  return metaDataObject;
}

export async function saveMetaDataPromise(path: string, metaData: Object): Promise<any> {
  const entryProperties = await PlatformIO.getPropertiesPromise(path);
  let metaFilePath;
  let newFsEntryMeta;
  if (entryProperties.isFile) {
    metaFilePath = getMetaFileLocationForFile(path);
    newFsEntryMeta = {
      ...metaData,
      appName: versionMeta.name,
      appVersionUpdated: versionMeta.version,
      lastUpdated: (new Date()).toJSON()
    };
  } else {
    const metaDirectoryPath = getMetaDirectoryPath(path);
    const metaDirectoryProperties = await PlatformIO.getPropertiesPromise(metaDirectoryPath);
    if (!metaDirectoryProperties) {
      await PlatformIO.createDirectoryPromise(metaDirectoryPath);
    }
    metaFilePath = getMetaFileLocationForDir(path);
    newFsEntryMeta = {
      ...metaData,
      appName: versionMeta.name,
      appVersionUpdated: versionMeta.version,
      lastUpdated: (new Date()).toJSON()
    };
  }
  const content = JSON.stringify(newFsEntryMeta);
  return PlatformIO.saveTextFilePromise(metaFilePath, content, true);
}

export function findColorForFileEntry(
  fileExtension: string,
  isFile: boolean,
  supportedFileTypes: Array<Object>,
): string {
  if (!isFile) {
    return AppConfig.defaultFolderColor;
  }
  let color = AppConfig.defaultFileColor;
  supportedFileTypes.map(fileType => {
    if (fileType.type.toLowerCase() === fileExtension.toLowerCase()) {
      if (fileType.color) {
        color = fileType.color;
      }
    }
    return true;
  });
  return color;
}

export function loadFileContentPromise(
  fullPath: string,
  type: string
): Promise<any> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', fullPath, true);
    xhr.responseType = type || 'arraybuffer';
    xhr.onerror = reject;
    xhr.onload = () => {
      const response = xhr.response || xhr.responseText;
      if (response) {
        resolve(response);
      } else {
        reject('loadFileContentPromise error');
      }
    };
    xhr.send();
  });
}
<commit_msg>added support for sidecar colors<commit_after>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import { saveAs } from 'file-saver';
import uuidv1 from 'uuid';
import PlatformIO from './platform-io';
import AppConfig from '../config';
import {
  extractTagsAsObjects,
  extractFileExtension,
  cleanTrailingDirSeparator,
  getMetaDirectoryPath,
  getMetaFileLocationForFile,
  getMetaFileLocationForDir
} from '../utils/paths';
// import { formatDateTime4Tag } from '../utils/misc';
import versionMeta from '../version.json';
import { type Tag } from '../reducers/taglibrary';

export type FileSystemEntry = {
  uuid: string,
  name: string,
  isFile: boolean,
  extension: string,
  thumbPath?: string,
  textContent?: string,
  description: string,
  tags: Array<Tag>,
  size: number,
  lmdt: number,
  path: string
};

export type FileSystemEntryMeta = {
  description: string,
  tags: Array<Tag>,
  appVersionCreated: string,
  appName: string,
  appVersionUpdated: string,
  lastUpdated: string
};

export function enhanceEntry(entry: any): FileSystemEntry {
  let fileNameTags = [];
  if (entry.isFile) {
    fileNameTags = extractTagsAsObjects(entry.name);
  }
  let sidecarDescription = '';
  let sidecarColor = '';
  let uuid;
  let sidecarTags = [];
  if (entry.meta) {
    sidecarDescription = entry.meta.description || '';
    sidecarColor = entry.meta.color || '';
    sidecarTags = entry.meta.tags || [];
    sidecarTags.map((tag) => { tag.type = 'sidecar'; return true; });
    uuid = entry.meta.uuid || undefined;
  }
  const enhancedEntry = {
    uuid: uuid || uuidv1(),
    name: entry.name,
    isFile: entry.isFile,
    extension: entry.isFile ? extractFileExtension(entry.name) : '',
    thumbPath: entry.thumbPath,
    description: sidecarDescription,
    color: sidecarColor,
    textContent: entry.textContent,
    tags: [...sidecarTags, ...fileNameTags],
    size: entry.size,
    lmdt: entry.lmdt,
    path: entry.path
  };
  // console.log('Enhancing ' + entry.path); console.log(enhancedEntry);
  return enhancedEntry;
}

export function createDirectoryIndex(directoryPath: string, extractText: boolean = false): Promise<Array<Object>> {
  const dirPath = cleanTrailingDirSeparator(directoryPath);
  if (PlatformIO.isWorkerAvailable() && !PlatformIO.haveObjectStoreSupport()) { // Start indexing in worker if not in the object store mode
    return PlatformIO.createDirectoryIndexInWorker(dirPath, extractText);
  }

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const directoryIndex = [];
      let counter = 0;
      console.time('createDirectoryIndex');
      walkDirectory(dirPath, { recursive: true, skipMetaFolder: true, extractText }, (fileEntry) => {
        counter += 1;
        if (counter > AppConfig.indexerLimit) {
          console.warn('Walk canceled by ' + AppConfig.indexerLimit);
          window.walkCanceled = true;
        }
        directoryIndex.push(enhanceEntry(fileEntry));
      }, (directoryEntry) => {
        if (directoryEntry.name !== AppConfig.metaFolder) {
          counter += 1;
          directoryIndex.push(enhanceEntry(directoryEntry));
        }
      }).then(() => { // entries - can be used for further processing
        window.walkCanceled = false;
        console.log('Directory index created ' + dirPath + ' containing ' + directoryIndex.length);
        console.timeEnd('createDirectoryIndex');
        resolve(directoryIndex);
        return true;
      }).catch((err) => {
        window.walkCanceled = false;
        console.timeEnd('createDirectoryIndex');
        console.warn('Error creating index: ' + err);
        reject(err);
      });
    }, 2000);
  });
}

export function walkDirectory(
  path: string,
  options: Object = {},
  fileCallback: any,
  dirCallback: any
) {
  const mergedOptions = {
    recursive: false,
    skipMetaFolder: true,
    loadMetaDate: true,
    extractText: false,
    ...options
  };
  return PlatformIO.listDirectoryPromise(path, false, mergedOptions.extractText).then((entries) => {
    if (window.walkCanceled) {
      return false;
    }
    return Promise.all(entries.map((entry) => {
      if (window.walkCanceled) {
        return false;
      }

      if (entry.isFile) {
        if (fileCallback) {
          fileCallback(entry);
        }
        return entry;
      }

      if (dirCallback) {
        dirCallback(entry);
      }

      if (mergedOptions.recursive) {
        if (mergedOptions.skipMetaFolder && entry.name === AppConfig.metaFolder) {
          return entry;
        }
        return walkDirectory(entry.path, mergedOptions, fileCallback, dirCallback);
      }
      return entry;
    }));
  }).catch((err) => {
    console.warn('Error walking directory ' + err);
    return err;
  });
}

export async function getAllPropertiesPromise(entryPath: string): Promise<FileSystemEntry> {
  const entryProps = await PlatformIO.getPropertiesPromise(entryPath);
  let metaFilePath;
  if (entryProps.isFile) {
    metaFilePath = getMetaFileLocationForFile(entryPath);
  } else {
    metaFilePath = getMetaFileLocationForDir(entryPath);
  }
  const metaFileProps = await PlatformIO.getPropertiesPromise(metaFilePath);
  if (metaFileProps.isFile) {
    entryProps.meta = await loadJSONFile(metaFilePath);
  }
  return enhanceEntry(entryProps);
}

export async function loadJSONFile(filePath: string): any {
  let jsonObject;
  let jsonContent = await PlatformIO.loadTextFilePromise(filePath);
  const UTF8_BOM = '\ufeff';
  if (jsonContent.indexOf(UTF8_BOM) === 0) {
    jsonContent = jsonContent.substring(1, jsonContent.length);
  }
  try {
    jsonObject = JSON.parse(jsonContent);
  } catch (err) {
    console.warn('Error parsing meta json file for ' + filePath + ' - ' + err);
  }
  return jsonObject;
}

export function saveAsTextFile(blob: any, filename: string) {
  saveAs(blob, filename);
}

export function openLinkExternally(uri: string) {
  const decodedURI = decodeURIComponent(uri);
  if (
    decodedURI.startsWith('http://') ||
    decodedURI.startsWith('https://') ||
    decodedURI.startsWith('file://')
  ) {
    PlatformIO.openFile(decodedURI);
  } else {
    console.log('Not supported URL format: ' + decodedURI);
  }
}

export function deleteFilesPromise(filePathList: Array<string>) {
  const fileDeletionPromises = [];
  filePathList.forEach((filePath) => {
    fileDeletionPromises.push(PlatformIO.deleteFilePromise(filePath));
  });
  return Promise.all(fileDeletionPromises);
}

export function renameFilesPromise(renameJobs: Array<Array<string>>) {
  const fileRenamePromises = [];
  renameJobs.forEach((renameJob) => {
    fileRenamePromises.push(PlatformIO.renameFilePromise(renameJob[0], renameJob[1]));
  });
  return Promise.all(fileRenamePromises);
}

export function copyFilesPromise(copyJobs: Array<Array<string>>) {
  const ioJobPromises = [];
  copyJobs.forEach((copyJob) => {
    ioJobPromises.push(PlatformIO.copyFilePromise(copyJob[0], copyJob[1]));
  });
  return Promise.all(ioJobPromises);
}

export async function loadSubFolders(path: string, loadHidden: boolean = false) {
  const folderContent = await PlatformIO.listDirectoryPromise(path, true);
  const subfolders = [];
  let i = 0;
  let isHidden = false;
  folderContent.map((entry) => {
    if (!entry.isFile) {
      isHidden = entry.name.startsWith('.');
      if (isHidden) {
        if (loadHidden) {
          subfolders.push({
            key: '0-' + (i += 1),
            isLeaf: false,
            name: entry.name,
            isFile: entry.isFile,
            lmdt: entry.lmdt,
            meta: entry.meta,
            path: entry.path,
            tags: entry.tags
          });
        } else {
          // do nothing
        }
      } else {
        subfolders.push({
          key: '0-' + (i += 1),
          isLeaf: false,
          name: entry.name,
          isFile: entry.isFile,
          lmdt: entry.lmdt,
          meta: entry.meta,
          path: entry.path,
          tags: entry.tags
        });
      }
    }
    return true;
  });
  return subfolders;
}

export function generateFileName(fileName: string, tags: Array<string>, tagDelimiter: string) {
  let tagsString = '';
  const prefixTagContainer = AppConfig.prefixTagContainer;
  // Creating the string will all the tags by more that 0 tags
  if (tags && tags.length > 0) {
    tagsString = AppConfig.beginTagContainer;
    for (let i = 0; i < tags.length; i += 1) {
      if (i === tags.length - 1) {
        tagsString += tags[i].trim();
      } else {
        tagsString += tags[i].trim() + tagDelimiter;
      }
    }
    tagsString = tagsString.trim() + AppConfig.endTagContainer;
  }
  // console.log('The tags string: ' + tagsString);
  const fileExt = extractFileExtension(fileName);
  // console.log('Filename: ' + fileName + ' file extension: ' + fileExt);
  // Assembling the new filename with the tags
  let newFileName = '';
  const beginTagContainer = fileName.indexOf(AppConfig.beginTagContainer);
  const endTagContainer = fileName.indexOf(AppConfig.endTagContainer);
  const lastDotPosition = fileName.lastIndexOf('.');
  if (beginTagContainer < 0 || endTagContainer < 0 || beginTagContainer >= endTagContainer) {
    // Filename does not contains tags.
    if (lastDotPosition < 0) {
      // File does not have an extension
      newFileName = fileName.trim() + tagsString;
    } else {
      // File has an extension
      newFileName = fileName.substring(0, lastDotPosition).trim() + prefixTagContainer + tagsString + '.' + fileExt;
    }
  } else {
    // File does not have an extension
    newFileName = fileName.substring(0, beginTagContainer).trim() + prefixTagContainer + tagsString + fileName.substring(endTagContainer + 1, fileName.length).trim();
  }
  if (newFileName.length < 1) {
    throw 'Generated filename is invalid';
  }
  // Removing double prefix
  newFileName = newFileName.split(prefixTagContainer + '' + prefixTagContainer).join(prefixTagContainer);
  return newFileName;
}

export async function loadMetaDataPromise(path: string): Promise<FileSystemEntryMeta> {
  const entryProperties = await PlatformIO.getPropertiesPromise(path);
  let metaDataObject;
  if (entryProperties.isFile) {
    const metaFilePath = getMetaFileLocationForFile(path);
    const metaData = await loadJSONFile(metaFilePath);
    metaDataObject = {
      description: metaData.description || '',
      tags: metaData.tags || [],
      appVersionCreated: metaData.appVersionCreated || '',
      appName: metaData.appName || '',
      appVersionUpdated: metaData.appVersionUpdated || '',
      lastUpdated: metaData.lastUpdated || ''
    };
  } else {
    const metaFilePath = getMetaFileLocationForDir(path);
    const metaData = await loadJSONFile(metaFilePath);
    metaDataObject = {
      description: metaData.description || '',
      tags: metaData.tags || [],
      appVersionCreated: metaData.appVersionCreated || '',
      appName: metaData.appName || '',
      appVersionUpdated: metaData.appVersionUpdated || '',
      lastUpdated: metaData.lastUpdated || ''
    };
  }
  return metaDataObject;
}

export async function saveMetaDataPromise(path: string, metaData: Object): Promise<any> {
  const entryProperties = await PlatformIO.getPropertiesPromise(path);
  let metaFilePath;
  let newFsEntryMeta;
  if (entryProperties.isFile) {
    metaFilePath = getMetaFileLocationForFile(path);
    newFsEntryMeta = {
      ...metaData,
      appName: versionMeta.name,
      appVersionUpdated: versionMeta.version,
      lastUpdated: (new Date()).toJSON()
    };
  } else {
    const metaDirectoryPath = getMetaDirectoryPath(path);
    const metaDirectoryProperties = await PlatformIO.getPropertiesPromise(metaDirectoryPath);
    if (!metaDirectoryProperties) {
      await PlatformIO.createDirectoryPromise(metaDirectoryPath);
    }
    metaFilePath = getMetaFileLocationForDir(path);
    newFsEntryMeta = {
      ...metaData,
      appName: versionMeta.name,
      appVersionUpdated: versionMeta.version,
      lastUpdated: (new Date()).toJSON()
    };
  }
  const content = JSON.stringify(newFsEntryMeta);
  return PlatformIO.saveTextFilePromise(metaFilePath, content, true);
}

export function findColorForFileEntry(
  fileExtension: string,
  isFile: boolean,
  supportedFileTypes: Array<Object>,
): string {
  if (!isFile) {
    return AppConfig.defaultFolderColor;
  }
  let color = AppConfig.defaultFileColor;
  supportedFileTypes.map(fileType => {
    if (fileType.type.toLowerCase() === fileExtension.toLowerCase()) {
      if (fileType.color) {
        color = fileType.color;
      }
    }
    return true;
  });
  return color;
}

export function loadFileContentPromise(
  fullPath: string,
  type: string
): Promise<any> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', fullPath, true);
    xhr.responseType = type || 'arraybuffer';
    xhr.onerror = reject;
    xhr.onload = () => {
      const response = xhr.response || xhr.responseText;
      if (response) {
        resolve(response);
      } else {
        reject('loadFileContentPromise error');
      }
    };
    xhr.send();
  });
}
<|endoftext|>


<commit_before>// adopted from: fast-apply <"https://github.com/JayceTDE/fast-apply>
// copyright: Jayce Pulsipher <jaycemp@gmail.com> (MIT)

// add type check for `function` to prevent errors by using identity

function fastApply (fn, context, args) {
  switch (args ? args.length : 0) {
    case 0:
      return context
        ? fn.call(context)
        : fn()
    case 1:
      return context
        ? fn.call(context, args[0])
        : fn(args[0])
    case 2:
      return context
        ? fn.call(context, args[0], args[1])
        : fn(args[0], args[1])
    case 3:
      return context
        ? fn.call(context, args[0], args[1], args[2])
        : fn(args[0], args[1], args[2])
    case 4:
      return context
        ? fn.call(context, args[0], args[1], args[2], args[3])
        : fn(args[0], args[1], args[2], args[3])
    case 5:
      return context
        ? fn.call(context, args[0], args[1], args[2], args[3], args[4])
        : fn(args[0], args[1], args[2], args[3], args[4])
    case 6:
      return context
        ? fn.call(context, args[0], args[1], args[2], args[3], args[4], args[5])
        : fn(args[0], args[1], args[2], args[3], args[4], args[5])
    default: return fn.apply(context, args)
  }
}

// concept adopted from: https://github.com/wilmoore/apply-or.js
// reasoning: Function.prototype.apply is normally sufficient; however, there are situations where it is useful to treat a value as a Function (invoke it) only if it is indeed a Function; otherwise, return it as-is. Calling .apply on a value that is not a function would cause an error.
function fastApplyNoThrow (fn, context, args) {
  fn = (typeof fn !== 'function')
    ? identity
    : fn

  return fastApply(fn, context, args)
}

function identity (value) {
  return value
}

module.exports = fastApply
module.exports.Or = fastApplyNoThrow
<commit_msg>update function/apply — remove noThrow function<commit_after>// adopted from: fast-apply <"https://github.com/JayceTDE/fast-apply>
// copyright: Jayce Pulsipher <jaycemp@gmail.com> (MIT)

function fastApply (fn, context, args) {
  switch (args ? args.length : 0) {
    case 0:
      return context
        ? fn.call(context)
        : fn()
    case 1:
      return context
        ? fn.call(context, args[0])
        : fn(args[0])
    case 2:
      return context
        ? fn.call(context, args[0], args[1])
        : fn(args[0], args[1])
    case 3:
      return context
        ? fn.call(context, args[0], args[1], args[2])
        : fn(args[0], args[1], args[2])
    case 4:
      return context
        ? fn.call(context, args[0], args[1], args[2], args[3])
        : fn(args[0], args[1], args[2], args[3])
    case 5:
      return context
        ? fn.call(context, args[0], args[1], args[2], args[3], args[4])
        : fn(args[0], args[1], args[2], args[3], args[4])
    case 6:
      return context
        ? fn.call(context, args[0], args[1], args[2], args[3], args[4], args[5])
        : fn(args[0], args[1], args[2], args[3], args[4], args[5])
    default: return fn.apply(context, args)
  }
}

module.exports = fastApply
<|endoftext|>