<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

(function () {
    "use strict";

    var util = require("util"),
        EventEmitter = require("events").EventEmitter;

    var Q = require("q");

    var Document = require("./dom/document");

    var OPEN_DOCUMENTS_CHANGE_HYSTERESIS = 300,
        ACTIVE_DOCUMENT_CHANGE_HYSTERESIS = 100;

    /**
     * Return the keys for a set as integers.
     *
     * @private
     * @param {{number: *}} A set
     * @return {Array.<number>} The keys of the set as integers
     */
    function _intKeys(set) {
        return Object.keys(set).map(function (key) {
            return parseInt(key, 10);
        });
    }

    /**
     * The DocumentManager provides a simple interface for requesting and maintaining
     * up-to-date Document objects from Photoshop.
     *
     * Emits "openDocumentsChanged" event when the set of open documents changes with
     * the following parameters:
     *      1. @param {Array.<number>} IDs for the set of currently open documents
     *      2. @param {Array.<number>} IDs for the set of recently opened documents
     *      3. @param {Array.<number>} IDs for the set of recently closed documents
     *
     * Emits "activeDocumentChanged" event when the currently active document changes
     * with the follwing parameter:
     *      1. @param {?number} ID of the currently active document, or null if there is none
     *
     * @constructor
     * @param {Generator} generator
     * @param {object} config
     * @param {Logger} logger
     */
    function DocumentManager(generator, config, logger) {
        EventEmitter.call(this);

        this._generator = generator;
        this._config = config;
        this._logger = logger;

        this._documents = {};
        this._documentDeferreds = {};
        this._documentChanges = {};

        this._openDocumentIds = {};
        this._newOpenDocumentIds = {};
        this._newClosedDocumentIds = {};

        this._initActiveDocumentID();
        this._resetOpenDocumentIDs()
            .then(function () {
                // make sure that openDocumentsChanged fires once on startup, even
                // if there are no open documents
                this._handleOpenDocumentsChange();
            }.bind(this))
            .done();

        generator.onPhotoshopEvent("imageChanged", this._handleImageChanged.bind(this));
    }

    util.inherits(DocumentManager, EventEmitter);

    /**
     * The Generator instance.
     *
     * @private
     * @type {Generator}
     */
    DocumentManager.prototype._generator = null;

    /**
     * A set of per-document-ID up-to-date Document objects.
     *
     * @private
     * @type {{number: Document}}
     */
    DocumentManager.prototype._documents = null;

    /**
     * A set of per-document-ID deferred objects that indicate Document creation in progress.
     *
     * @private
     * @type {{number: Deferred}}
     */
    DocumentManager.prototype._documentDeferreds = null;

    /**
     * A set of per-document-ID change queues.
     *
     * @private
     * @type {{number: Array.<object>}}
     */
    DocumentManager.prototype._documentChanges = null;

    /**
     * A set of document IDs for the currently open documents.
     *
     * @private
     * @type {{number: boolean}}
     */
    DocumentManager.prototype._openDocumentIds = null;

    /**
     * A set of recently opened document IDs.
     *
     * @private
     * @type {{number: boolean}}
     */
    DocumentManager.prototype._newOpenDocumentIds = null;

    /**
     * A set of recently closed document IDs.
     *
     * @private
     * @type {{number: boolean}}
     */
    DocumentManager.prototype._newClosedDocumentIds = null;

    /**
     * Whether the set of currently open documents is being updated.
     *
     * @private
     * @type {boolean}
     */
    DocumentManager.prototype._openDocumentIdsUpdating = false;

    /**
     * Whether the set of currently open documents needs to be updated.
     *
     * @private
     * @type {boolean}
     */
    DocumentManager.prototype._openDocumentIdsStale = false;

    /**
     * The ID of the currently active document, or null if there is none.
     *
     * @private
     * @type {?number}
     */
    DocumentManager.prototype._activeDocumentId = null;

    /**
     * Asynchronously create a new Document object using the full document
     * description from Photoshop.
     *
     * @private
     * @param {!number} id The ID of the Document to create
     * @return {Promise.<Document>} A promis that resolves with a new Document object for the given ID.
     */
    DocumentManager.prototype._getEntireDocument = function (id) {
        return this._generator.getDocumentInfo(id).then(function (raw) {
            // this._logger.debug(JSON.stringify(raw, null, "  "));
            return new Document(this._generator, this._config, this._logger, raw);
        }.bind(this));
    };

    /**
     * Asynchronously re-initialize the Document object for a given document ID,
     * discarding the previous Document object and clearing the change queue for
     * that ID.
     *
     * @private
     * @param {!number} id The ID of the Document to re-initialize
     */
    DocumentManager.prototype._resetDocument = function (id) {
        this._documentChanges[id] = [];
        delete this._documents[id];

        this._getEntireDocument(id).done(function (document) {
            // Dispose of this document reference when the document is closed in Photoshop
            document.on("closed", function () {
                delete this._documents[id];
                delete this._documentChanges[id];

                if (this._documentDeferreds.hasOwnProperty(id)) {
                    this._documentDeferreds[id].reject();
                    delete this._documentDeferreds[id];
                }
            }.bind(this));

            this._documents[id] = document;
            this._processNextChange(id);
        }.bind(this), function (err) {
            this._logger.error("Failed to get document:", err);
            this._documentDeferreds[id].reject(err);
        }.bind(this));
    };

    /**
     * Asynchronously initialize a Document object for the given document ID.
     *
     * @private
     * @param {!number} id The ID of the Document to initialize
     * @return {Promise.<Document>} A promise that resolves with the up-to-date Document
     */
    DocumentManager.prototype._initDocument = function (id) {
        var deferred = Q.defer();

        this._documentDeferreds[id] = deferred;
        this._resetDocument(id);

        return deferred;
    };

    /**
     * For the given document change queue, attempt to apply the next
     * change from the queue to the appropriate Document. If unable to
     * apply the change, re-request the entire document. Otherwise,
     * continue processing changes from the change queue.
     *
     * @private
     * @param {!number} id The document ID that indicates the change queue to process
     */
    DocumentManager.prototype._processNextChange = function (id) {
        var document = this._documents[id],
            changes = this._documentChanges[id],
            deferred = this._documentDeferreds[id];

        if (!changes || !deferred) {
            // The document was closed while processing changes
            return;
        }

        if (changes.length === 0) {
            deferred.resolve(document);
            delete this._documentDeferreds[id];
            return;
        }

        var change = changes.shift();
        this._logger.debug("Applying change: ", JSON.stringify(change, null, "  "));

        var success = document._applyChange(change);
        if (!success) {
            this._logger.warn("Unable to apply change to document");
            this._resetDocument(id);
        } else {
            this._processNextChange(id);
        }
    };

    /**
     * Asynchronously initialize the current active document ID.
     *
     * @private
     */
    DocumentManager.prototype._initActiveDocumentID = function () {
        this._generator.getDocumentInfo(undefined, {
            compInfo:           false,
            imageInfo:          false,
            layerInfo:          false,
            expandSmartObjects: false,
            getTextStyles:      false,
            selectedLayers:     false,
            getCompSettings:    false
        }).then(function (document) {
            if (document) {
                this._handleActiveDocumentChange(document.id);
            } else {
                this._handleActiveDocumentChange(null);
            }
        }.bind(this)).fail(function (err) {
            this._logger.warn(err);
            this._handleActiveDocumentChange(null);
        }.bind(this)).done();
    };

    /**
     * Asynchronously reset the set of open document IDs.
     *
     * @private
     */
    DocumentManager.prototype._resetOpenDocumentIDs = function () {
        if (this._openDocumentIdsUpdating) {
            this._openDocumentIdsStale = true;
            return;
        }

        this._openDocumentIdsUpdating = true;

        return this._generator.getOpenDocumentIDs()
            .then(function (ids) {
                var originalIds = Object.keys(this._openDocumentIds);

                ids.forEach(function (id) {
                    if (!this._openDocumentIds.hasOwnProperty(id)) {
                        this._addOpenDocumentID(id);
                    }
                }, this);

                var newIds = ids.reduce(function (ids, id) {
                    ids[id] = true;
                    return ids;
                }, {});

                originalIds.forEach(function (id) {
                    if (!newIds.hasOwnProperty(id)) {
                        this._removeOpenDocumentID(id);
                    }
                }, this);

                this._openDocumentIdsUpdating = false;

                if (this._openDocumentIdsStale) {
                    this._openDocumentIdsStale = false;
                    this._resetOpenDocumentIDs().done();
                }
            }.bind(this));
    };

    /**
     * Emits an "openDocumentsChanged" changed event that includes the currently
     * open set of document IDs, along with recently opened and closed documentIDs.
     *
     * @private
     * @param {number} id
     */
    DocumentManager.prototype._handleOpenDocumentsChange = function () {
        if (this._openDocumentsChangeTimer) {
            return;
        }

        this._openDocumentsChangeTimer = setTimeout(function () {
            var allOpenDocumentIds = _intKeys(this._openDocumentIds),
                nowOpenDocumentIds = _intKeys(this._newOpenDocumentIds),
                nowClosedDocumentIds = _intKeys(this._newClosedDocumentIds);

            this._newOpenDocumentIds = {};
            this._newClosedDocumentIds = {};
            this._openDocumentsChangeTimer = null;

            this.emit("openDocumentsChanged", allOpenDocumentIds, nowOpenDocumentIds, nowClosedDocumentIds);
        }.bind(this), OPEN_DOCUMENTS_CHANGE_HYSTERESIS);
    };

    /**
     * Add the given document ID from the set of currently open documents.
     *
     * @private
     * @param {number} id
     */
    DocumentManager.prototype._addOpenDocumentID = function (id) {
        if (this._openDocumentIds.hasOwnProperty(id)) {
            return;
        }

        this._openDocumentIds[id] = true;
        this._newOpenDocumentIds[id] = true;
        delete this._newClosedDocumentIds[id];
        this._handleOpenDocumentsChange();
    };

    /**
     * Remove the given document ID from the set of currently open documents.
     *
     * @private
     * @param {number} id
     */
    DocumentManager.prototype._removeOpenDocumentID = function (id) {
        if (id === this._activeDocumentId) {
            this._handleActiveDocumentChange(null);
        }

        if (!this._openDocumentIds.hasOwnProperty(id)) {
            return;
        }

        delete this._openDocumentIds[id];
        this._newClosedDocumentIds[id] = true;
        delete this._newOpenDocumentIds[id];
        this._handleOpenDocumentsChange();
    };

    /**
     * Asynchronously emits an "activeDocumentChanged" event when the currently
     * active document changes. If there is a currently active document, that
     * document's ID is included with the the "activeDocumentChanged" event;
     * otherwise the parameter is null.
     *
     * @private
     * @param {?number} id
     */
    DocumentManager.prototype._handleActiveDocumentChange = function (id) {
        this._activeDocumentId = id;

        if (this._activeDocumentChangeTimer) {
            return;
        }

        this._activeDocumentChangeTimer = setTimeout(function () {
            this._activeDocumentChangeTimer = null;

            this.emit("activeDocumentChanged", this._activeDocumentId);
        }.bind(this), ACTIVE_DOCUMENT_CHANGE_HYSTERESIS);
    };

    /**
     * Handler for Photoshop's imageChanged event. Accepts a raw change description object
     * and, if the change is intended for an extant Document object, updates that object
     * accordingly. Ignores changes for document IDs for which getDocument has not been
     * called. The imageChanged events are also used to maintain the current set of open
     * documents as well as the current active document.
     *
     * @private
     * @param {object} change A raw change description object
     */
    DocumentManager.prototype._handleImageChanged = function (change) {
        if (!change.hasOwnProperty("id")) {
            this._logger.warn("Received change for unknown document:", change);
            return;
        }

        var id = change.id;

        // Update the active document and the set of open documents
        if (change.active) {
            this._resetOpenDocumentIDs().done();
            this._handleActiveDocumentChange(id);
        } else if (change.closed) {
            this._removeOpenDocumentID(id);
        } else {
            this._addOpenDocumentID(id);
        }

        // ignore changes for document IDs until a client calls getDocument
        if (!this._documentDeferreds.hasOwnProperty(id) && !this._documents.hasOwnProperty(id)) {
            return;
        }

        if (!this._documentChanges.hasOwnProperty(id)) {
            this._documentChanges[id] = [];
        }

        var changes = this._documentChanges[id],
            pendingChanges = changes.push(change);

        if (pendingChanges === 1 && !this._documentDeferreds.hasOwnProperty(id)) {
            if (this._documents.hasOwnProperty(id)) {
                this._documentDeferreds[id] = Q.defer();
                this._processNextChange(id);
            } else {
                this._initDocument(id);
            }
        }
    };

    /**
     * Asynchonously request an up-to-date Document object for the given document ID.
     *
     * @param {!number} id The document ID
     * @return {Promise.<Document>} A promise that resoves with a Document object for the given ID
     */
    DocumentManager.prototype.getDocument = function (id) {
        // We're in the process of updating the document; return that when it's ready
        if (this._documentDeferreds.hasOwnProperty(id)) {
            return this._documentDeferreds[id].promise;
        }

        // We have a document and we aren't updating it; return it immediately
        if (this._documents.hasOwnProperty(id)) {
            return new Q(this._documents[id]);
        }

        // We don't know anything about this document; fetch it from Photoshop
        var deferred = this._initDocument(id);

        return deferred.promise;
    };

    /**
     * Get the ID of the currently active document, or null if there isn't one.
     *
     * @return {?number] ID of the currently active document.
     */
    DocumentManager.prototype.getActiveDocumentID = function () {
        return this._activeDocumentId;
    };

    /**
     * Asynchonously request an up-to-date Document object for the currently
     * active document.
     *
     * @see DocumentManager.prototype.getDocument
     * @see DocumentManager.prototype.getActiveDocumentID
     * @param {!number} id The document ID
     * @return {Promise.<Document>} A promise that resoves with a Document object
     *      for the currently active document, or rejects if there is none.
     */
    DocumentManager.prototype.getActiveDocument = function () {
        if (this._activeDocumentId) {
            return this.getDocument(this._activeDocumentId);
        } else {
            return Q.reject();
        }
    };

    /**
     * Get the IDs of the currently open documents.
     *
     * @return {Array.<number>} IDs of the currently open documents
     */
    DocumentManager.prototype.getOpenDocumentIDs = function () {
        return _intKeys(this._openDocumentIds);
    };

    module.exports = DocumentManager;
}());<commit_msg>Ensure that DocumentManager._resetOpenDocumentIds always returns a promise<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

(function () {
    "use strict";

    var util = require("util"),
        EventEmitter = require("events").EventEmitter;

    var Q = require("q");

    var Document = require("./dom/document");

    var OPEN_DOCUMENTS_CHANGE_HYSTERESIS = 300,
        ACTIVE_DOCUMENT_CHANGE_HYSTERESIS = 100;

    /**
     * Return the keys for a set as integers.
     *
     * @private
     * @param {{number: *}} A set
     * @return {Array.<number>} The keys of the set as integers
     */
    function _intKeys(set) {
        return Object.keys(set).map(function (key) {
            return parseInt(key, 10);
        });
    }

    /**
     * The DocumentManager provides a simple interface for requesting and maintaining
     * up-to-date Document objects from Photoshop.
     *
     * Emits "openDocumentsChanged" event when the set of open documents changes with
     * the following parameters:
     *      1. @param {Array.<number>} IDs for the set of currently open documents
     *      2. @param {Array.<number>} IDs for the set of recently opened documents
     *      3. @param {Array.<number>} IDs for the set of recently closed documents
     *
     * Emits "activeDocumentChanged" event when the currently active document changes
     * with the follwing parameter:
     *      1. @param {?number} ID of the currently active document, or null if there is none
     *
     * @constructor
     * @param {Generator} generator
     * @param {object} config
     * @param {Logger} logger
     */
    function DocumentManager(generator, config, logger) {
        EventEmitter.call(this);

        this._generator = generator;
        this._config = config;
        this._logger = logger;

        this._documents = {};
        this._documentDeferreds = {};
        this._documentChanges = {};

        this._openDocumentIds = {};
        this._newOpenDocumentIds = {};
        this._newClosedDocumentIds = {};

        this._initActiveDocumentID();
        this._resetOpenDocumentIDs()
            .then(function () {
                // make sure that openDocumentsChanged fires once on startup, even
                // if there are no open documents
                this._handleOpenDocumentsChange();
            }.bind(this))
            .done();

        generator.onPhotoshopEvent("imageChanged", this._handleImageChanged.bind(this));
    }

    util.inherits(DocumentManager, EventEmitter);

    /**
     * The Generator instance.
     *
     * @private
     * @type {Generator}
     */
    DocumentManager.prototype._generator = null;

    /**
     * A set of per-document-ID up-to-date Document objects.
     *
     * @private
     * @type {{number: Document}}
     */
    DocumentManager.prototype._documents = null;

    /**
     * A set of per-document-ID deferred objects that indicate Document creation in progress.
     *
     * @private
     * @type {{number: Deferred}}
     */
    DocumentManager.prototype._documentDeferreds = null;

    /**
     * A set of per-document-ID change queues.
     *
     * @private
     * @type {{number: Array.<object>}}
     */
    DocumentManager.prototype._documentChanges = null;

    /**
     * A set of document IDs for the currently open documents.
     *
     * @private
     * @type {{number: boolean}}
     */
    DocumentManager.prototype._openDocumentIds = null;

    /**
     * A set of recently opened document IDs.
     *
     * @private
     * @type {{number: boolean}}
     */
    DocumentManager.prototype._newOpenDocumentIds = null;

    /**
     * A set of recently closed document IDs.
     *
     * @private
     * @type {{number: boolean}}
     */
    DocumentManager.prototype._newClosedDocumentIds = null;

    /**
     * If non-null, resolves once the set of open document IDs is finished updating
     *
     * @private
     * @type {?Promise}
     */
    DocumentManager.prototype._openDocumentIdsUpdatingPromise = null;

    /**
     * Whether the set of currently open documents needs to be updated.
     *
     * @private
     * @type {boolean}
     */
    DocumentManager.prototype._openDocumentIdsStale = false;

    /**
     * The ID of the currently active document, or null if there is none.
     *
     * @private
     * @type {?number}
     */
    DocumentManager.prototype._activeDocumentId = null;

    /**
     * Asynchronously create a new Document object using the full document
     * description from Photoshop.
     *
     * @private
     * @param {!number} id The ID of the Document to create
     * @return {Promise.<Document>} A promis that resolves with a new Document object for the given ID.
     */
    DocumentManager.prototype._getEntireDocument = function (id) {
        return this._generator.getDocumentInfo(id).then(function (raw) {
            // this._logger.debug(JSON.stringify(raw, null, "  "));
            return new Document(this._generator, this._config, this._logger, raw);
        }.bind(this));
    };

    /**
     * Asynchronously re-initialize the Document object for a given document ID,
     * discarding the previous Document object and clearing the change queue for
     * that ID.
     *
     * @private
     * @param {!number} id The ID of the Document to re-initialize
     */
    DocumentManager.prototype._resetDocument = function (id) {
        this._documentChanges[id] = [];
        delete this._documents[id];

        this._getEntireDocument(id).done(function (document) {
            // Dispose of this document reference when the document is closed in Photoshop
            document.on("closed", function () {
                delete this._documents[id];
                delete this._documentChanges[id];

                if (this._documentDeferreds.hasOwnProperty(id)) {
                    this._documentDeferreds[id].reject();
                    delete this._documentDeferreds[id];
                }
            }.bind(this));

            this._documents[id] = document;
            this._processNextChange(id);
        }.bind(this), function (err) {
            this._logger.error("Failed to get document:", err);
            this._documentDeferreds[id].reject(err);
        }.bind(this));
    };

    /**
     * Asynchronously initialize a Document object for the given document ID.
     *
     * @private
     * @param {!number} id The ID of the Document to initialize
     * @return {Promise.<Document>} A promise that resolves with the up-to-date Document
     */
    DocumentManager.prototype._initDocument = function (id) {
        var deferred = Q.defer();

        this._documentDeferreds[id] = deferred;
        this._resetDocument(id);

        return deferred;
    };

    /**
     * For the given document change queue, attempt to apply the next
     * change from the queue to the appropriate Document. If unable to
     * apply the change, re-request the entire document. Otherwise,
     * continue processing changes from the change queue.
     *
     * @private
     * @param {!number} id The document ID that indicates the change queue to process
     */
    DocumentManager.prototype._processNextChange = function (id) {
        var document = this._documents[id],
            changes = this._documentChanges[id],
            deferred = this._documentDeferreds[id];

        if (!changes || !deferred) {
            // The document was closed while processing changes
            return;
        }

        if (changes.length === 0) {
            deferred.resolve(document);
            delete this._documentDeferreds[id];
            return;
        }

        var change = changes.shift();
        this._logger.debug("Applying change: ", JSON.stringify(change, null, "  "));

        var success = document._applyChange(change);
        if (!success) {
            this._logger.warn("Unable to apply change to document");
            this._resetDocument(id);
        } else {
            this._processNextChange(id);
        }
    };

    /**
     * Asynchronously initialize the current active document ID.
     *
     * @private
     */
    DocumentManager.prototype._initActiveDocumentID = function () {
        this._generator.getDocumentInfo(undefined, {
            compInfo:           false,
            imageInfo:          false,
            layerInfo:          false,
            expandSmartObjects: false,
            getTextStyles:      false,
            selectedLayers:     false,
            getCompSettings:    false
        }).then(function (document) {
            if (document) {
                this._handleActiveDocumentChange(document.id);
            } else {
                this._handleActiveDocumentChange(null);
            }
        }.bind(this)).fail(function (err) {
            this._logger.warn(err);
            this._handleActiveDocumentChange(null);
        }.bind(this)).done();
    };

    /**
     * Asynchronously reset the set of open document IDs.
     *
     * Only one instance of this method will execute at a time. Concurrent
     * executions will result in the method running a second time after the
     * first instance has finished.
     *
     * @private
     * @return {!Promise} Resolves once the set of open document IDs has been
     *      updated.
     */
    DocumentManager.prototype._resetOpenDocumentIDs = function () {
        if (this._openDocumentIdsUpdatingPromise) {
            this._openDocumentIdsStale = true;
            return this._openDocumentIdsUpdatingPromise;
        }

        var promise = this._generator.getOpenDocumentIDs()
            .then(function (ids) {
                var originalIds = Object.keys(this._openDocumentIds);

                ids.forEach(function (id) {
                    if (!this._openDocumentIds.hasOwnProperty(id)) {
                        this._addOpenDocumentID(id);
                    }
                }, this);

                var newIds = ids.reduce(function (ids, id) {
                    ids[id] = true;
                    return ids;
                }, {});

                originalIds.forEach(function (id) {
                    if (!newIds.hasOwnProperty(id)) {
                        this._removeOpenDocumentID(id);
                    }
                }, this);

                // In the case that there is an additional pending call to _resetOpenDocumentIDs,
                // then we will re-call this function synchronously below. In order to
                // not hit the early return, we need to clear the variable holding a reference to
                // the orignal promise. (Or, in the normal case, this is just cleaning up after
                // ourselves.)
                this._openDocumentIdsUpdatingPromise = null;

                if (this._openDocumentIdsStale) {
                    this._openDocumentIdsStale = false;

                    // Returning a new promise in this "then" handler has the effect of not resolving
                    // the original promise until we're done with the next update.
                    return this._resetOpenDocumentIDs();
                }
            }.bind(this));

        this._openDocumentIdsUpdatingPromise = promise;

        return promise;
    };

    /**
     * Emits an "openDocumentsChanged" changed event that includes the currently
     * open set of document IDs, along with recently opened and closed documentIDs.
     *
     * @private
     * @param {number} id
     */
    DocumentManager.prototype._handleOpenDocumentsChange = function () {
        if (this._openDocumentsChangeTimer) {
            return;
        }

        this._openDocumentsChangeTimer = setTimeout(function () {
            var allOpenDocumentIds = _intKeys(this._openDocumentIds),
                nowOpenDocumentIds = _intKeys(this._newOpenDocumentIds),
                nowClosedDocumentIds = _intKeys(this._newClosedDocumentIds);

            this._newOpenDocumentIds = {};
            this._newClosedDocumentIds = {};
            this._openDocumentsChangeTimer = null;

            this.emit("openDocumentsChanged", allOpenDocumentIds, nowOpenDocumentIds, nowClosedDocumentIds);
        }.bind(this), OPEN_DOCUMENTS_CHANGE_HYSTERESIS);
    };

    /**
     * Add the given document ID from the set of currently open documents.
     *
     * @private
     * @param {number} id
     */
    DocumentManager.prototype._addOpenDocumentID = function (id) {
        if (this._openDocumentIds.hasOwnProperty(id)) {
            return;
        }

        this._openDocumentIds[id] = true;
        this._newOpenDocumentIds[id] = true;
        delete this._newClosedDocumentIds[id];
        this._handleOpenDocumentsChange();
    };

    /**
     * Remove the given document ID from the set of currently open documents.
     *
     * @private
     * @param {number} id
     */
    DocumentManager.prototype._removeOpenDocumentID = function (id) {
        if (id === this._activeDocumentId) {
            this._handleActiveDocumentChange(null);
        }

        if (!this._openDocumentIds.hasOwnProperty(id)) {
            return;
        }

        delete this._openDocumentIds[id];
        this._newClosedDocumentIds[id] = true;
        delete this._newOpenDocumentIds[id];
        this._handleOpenDocumentsChange();
    };

    /**
     * Asynchronously emits an "activeDocumentChanged" event when the currently
     * active document changes. If there is a currently active document, that
     * document's ID is included with the the "activeDocumentChanged" event;
     * otherwise the parameter is null.
     *
     * @private
     * @param {?number} id
     */
    DocumentManager.prototype._handleActiveDocumentChange = function (id) {
        this._activeDocumentId = id;

        if (this._activeDocumentChangeTimer) {
            return;
        }

        this._activeDocumentChangeTimer = setTimeout(function () {
            this._activeDocumentChangeTimer = null;

            this.emit("activeDocumentChanged", this._activeDocumentId);
        }.bind(this), ACTIVE_DOCUMENT_CHANGE_HYSTERESIS);
    };

    /**
     * Handler for Photoshop's imageChanged event. Accepts a raw change description object
     * and, if the change is intended for an extant Document object, updates that object
     * accordingly. Ignores changes for document IDs for which getDocument has not been
     * called. The imageChanged events are also used to maintain the current set of open
     * documents as well as the current active document.
     *
     * @private
     * @param {object} change A raw change description object
     */
    DocumentManager.prototype._handleImageChanged = function (change) {
        if (!change.hasOwnProperty("id")) {
            this._logger.warn("Received change for unknown document:", change);
            return;
        }

        var id = change.id;

        // Update the active document and the set of open documents
        if (change.active) {
            this._resetOpenDocumentIDs().done();
            this._handleActiveDocumentChange(id);
        } else if (change.closed) {
            this._removeOpenDocumentID(id);
        } else {
            this._addOpenDocumentID(id);
        }

        // ignore changes for document IDs until a client calls getDocument
        if (!this._documentDeferreds.hasOwnProperty(id) && !this._documents.hasOwnProperty(id)) {
            return;
        }

        if (!this._documentChanges.hasOwnProperty(id)) {
            this._documentChanges[id] = [];
        }

        var changes = this._documentChanges[id],
            pendingChanges = changes.push(change);

        if (pendingChanges === 1 && !this._documentDeferreds.hasOwnProperty(id)) {
            if (this._documents.hasOwnProperty(id)) {
                this._documentDeferreds[id] = Q.defer();
                this._processNextChange(id);
            } else {
                this._initDocument(id);
            }
        }
    };

    /**
     * Asynchonously request an up-to-date Document object for the given document ID.
     *
     * @param {!number} id The document ID
     * @return {Promise.<Document>} A promise that resoves with a Document object for the given ID
     */
    DocumentManager.prototype.getDocument = function (id) {
        // We're in the process of updating the document; return that when it's ready
        if (this._documentDeferreds.hasOwnProperty(id)) {
            return this._documentDeferreds[id].promise;
        }

        // We have a document and we aren't updating it; return it immediately
        if (this._documents.hasOwnProperty(id)) {
            return new Q(this._documents[id]);
        }

        // We don't know anything about this document; fetch it from Photoshop
        var deferred = this._initDocument(id);

        return deferred.promise;
    };

    /**
     * Get the ID of the currently active document, or null if there isn't one.
     *
     * @return {?number] ID of the currently active document.
     */
    DocumentManager.prototype.getActiveDocumentID = function () {
        return this._activeDocumentId;
    };

    /**
     * Asynchonously request an up-to-date Document object for the currently
     * active document.
     *
     * @see DocumentManager.prototype.getDocument
     * @see DocumentManager.prototype.getActiveDocumentID
     * @param {!number} id The document ID
     * @return {Promise.<Document>} A promise that resoves with a Document object
     *      for the currently active document, or rejects if there is none.
     */
    DocumentManager.prototype.getActiveDocument = function () {
        if (this._activeDocumentId) {
            return this.getDocument(this._activeDocumentId);
        } else {
            return Q.reject();
        }
    };

    /**
     * Get the IDs of the currently open documents.
     *
     * @return {Array.<number>} IDs of the currently open documents
     */
    DocumentManager.prototype.getOpenDocumentIDs = function () {
        return _intKeys(this._openDocumentIds);
    };

    module.exports = DocumentManager;
}());<|endoftext|>


<commit_before>
var Chart = Backbone.Model.extend({
  defaults : {
    'container' : 'current_chart'
  },

  initialize : function() {
    this.series = (this.get('type') == 'block') ? new BlockChartSeries() : new ChartSeries();
    this.bind('change:type', this.render);
    this.render();
  },

  container_node : function() {
    return $("#" + this.get("container"));
  },

  title_node : function() {
    return $("#charts_holder h3");
  },

  render : function() {
    var type = this.get('type');
    switch (type) {
      case 'bezier' :
        this.view = new BezierChartView({model : this});
        break;
      case 'horizontal_bar' :
        this.view = new HorizontalBarChartView({model : this});
        break;
      case 'mekko' :
        this.view = new MekkoChartView({model : this});
        break;
      case 'waterfall' :
        this.view = new WaterfallChartView({model : this});
        break;
      case 'vertical_stacked_bar' :
        this.view = new VerticalStackedBarChartView({model : this});
        break;
      case 'policy_bar' :
        this.view = new PolicyBarChartView({model : this});
        break;
      case 'policy_line' :
        this.view = new PolicyLineChartView({model : this});
        break;
      case 'block' :
        this.view = new BlockChartView({model : this});
        break;
      case 'vertical_bar' :
        this.view = new VerticalBarChartView({model : this});
        break;
      case 'html_table' :
        this.view = new HtmlTableChartView({model : this});
        break;
      default:
        this.view = new HtmlTableChartView({model : this});
    }
    this.title_node().html(this.get("name"));
    return this.view;
  },

  // @return [ApiResultArray] = [
  //   [[2010,0.4],[2040,0.6]],
  //   [[2010,20.4],2040,210.4]]
  // ]
  results : function(exclude_target) {
    var series;
    if (exclude_target == undefined || exclude_target == null){
      series =  this.series;
    }
    else{
      series =  this.non_target_series();
    }
    return _.map(series, function(serie) { return serie.result(); });
  },

  colors : function() {
    return this.series.map(function(serie) { return serie.get('color'); });
  },
  labels : function() {
    return this.series.map(function(serie) { return serie.get('label'); });
  },
  // @return [Float] Only values of the present
  values_present : function() {
    var exclude_target_series = true
    return _.map(this.results(exclude_target_series), function(result) { return result[0][1]; });
  },
  // @return [Float] Only values of the future
  values_future : function() {
    var exclude_target_series = true
    return _.map(this.results(exclude_target_series), function(result) { return result[1][1]; });
  },
  // @return [Float] All possible values. Helpful to determine min/max values
  values : function() {
    return _.flatten([this.values_present(), this.values_future()]);
  },
  // @return [[Float,Float]] Array of present/future values [Float,Float]
  value_pairs : function() {
    return _.map(this.series, function(serie) { return serie.result_pairs(); });
  },
  non_target_series : function() {
    return this.series
      .reject(function(serie) { return serie.get('is_target'); });
  },
  target_series : function() {
    return this.series
      .select(function(serie) { return serie.get('is_target'); });
  },
  // @return Array of present and future target
  target_results : function() {
    return _.flatten(this.target_series().map(function(serie) { return serie.result()[1][1]; }));
  },
  // @return Array of hashes {label, present_value, future_value}
  series_hash : function() {
    return this.series.map(function(serie) {
      var res = serie.result();
      var out = {
        label : serie.get('label'),
        present_value : res[0][1],
        future_value : res[1][1]
      };
      return out;
    });
  }
});

var ChartList = Backbone.Collection.extend({
  model : Chart,

  initialize : function() {
    //_.bindAll(this, 'change_chart');
    //this.bind('add', this.change_chart);
  },

  change : function(chart) {
    var old_chart = this.first();
    if (old_chart !== undefined) {
      this.remove(old_chart);
    }
    this.add(chart);
  },

  load : function(chart_id) {
    if (this.first().get('id')+'' == chart_id + '') {
      // if chart_id == currently shown chart, skip.
      return;
    }
    var url = '/output_elements/'+chart_id+'.js?'+timestamp();
    $.getScript(url, function() {
      App.call_api('');
      // show/hide default chart button
      if(chart_id != charts.current_default_chart) {
        $("a.default_charts").show();
      } else {
        $("a.default_charts").hide();
      }
      // update chart information link
      $("#output_element_actions a.chart_info").attr("href", "/descriptions/charts/" + chart_id);
      // update the position of the output_element_actions
      $("#output_element_actions").removeClass();
      $("#output_element_actions").addClass(charts.first().get("type"));
    });
  }
});
window.charts = new ChartList();
<commit_msg>ie fix part 3<commit_after>
var Chart = Backbone.Model.extend({
  defaults : {
    'container' : 'current_chart'
  },

  initialize : function() {
    this.series = (this.get('type') == 'block') ? new BlockChartSeries() : new ChartSeries();
    this.bind('change:type', this.render);
    this.render();
  },

  container_node : function() {
    return $("#" + this.get("container"));
  },

  title_node : function() {
    return $("#charts_holder h3");
  },

  render : function() {
    var type = this.get('type');
    switch (type) {
      case 'bezier' :
        this.view = new BezierChartView({model : this});
        break;
      case 'horizontal_bar' :
        this.view = new HorizontalBarChartView({model : this});
        break;
      case 'mekko' :
        this.view = new MekkoChartView({model : this});
        break;
      case 'waterfall' :
        this.view = new WaterfallChartView({model : this});
        break;
      case 'vertical_stacked_bar' :
        this.view = new VerticalStackedBarChartView({model : this});
        break;
      case 'policy_bar' :
        this.view = new PolicyBarChartView({model : this});
        break;
      case 'policy_line' :
        this.view = new PolicyLineChartView({model : this});
        break;
      case 'block' :
        this.view = new BlockChartView({model : this});
        break;
      case 'vertical_bar' :
        this.view = new VerticalBarChartView({model : this});
        break;
      case 'html_table' :
        this.view = new HtmlTableChartView({model : this});
        break;
      default:
        this.view = new HtmlTableChartView({model : this});
    }
    this.title_node().html(this.get("name"));
    return this.view;
  },

  // @return [ApiResultArray] = [
  //   [[2010,0.4],[2040,0.6]],
  //   [[2010,20.4],2040,210.4]]
  // ]
  results : function(exclude_target) {
    var series;
    if (exclude_target == undefined || exclude_target == null){
      series =  this.series;
    }
    else{
      series =  this.non_target_series();
    }
    return _.map(series, function(serie) { return serie.result(); });
  },

  colors : function() {
    return this.series.map(function(serie) { return serie.get('color'); });
  },
  labels : function() {
    return this.series.map(function(serie) { return serie.get('label'); });
  },
  // @return [Float] Only values of the present
  values_present : function() {
    var exclude_target_series = true
    return _.map(this.results(exclude_target_series), function(result) { return result[0][1]; });
  },
  // @return [Float] Only values of the future
  values_future : function() {
    var exclude_target_series = true
    return _.map(this.results(exclude_target_series), function(result) { return result[1][1]; });
  },
  // @return [Float] All possible values. Helpful to determine min/max values
  values : function() {
    return _.flatten([this.values_present(), this.values_future()]);
  },
  // @return [[Float,Float]] Array of present/future values [Float,Float]
  value_pairs : function() {
    return _.map(this.series, function(serie) { return serie.result_pairs(); });
  },
  non_target_series : function() {
    return this.series.reject(function(serie) { return serie.get('is_target'); });
  },
  target_series : function() {
    return this.series.select(function(serie) { return serie.get('is_target'); });
  },
  // @return Array of present and future target
  target_results : function() {
    return _.flatten(_.map(this.target_series(), function(serie) { return serie.result()[1][1]; }));
  },
  // @return Array of hashes {label, present_value, future_value}
  series_hash : function() {
    return this.series.map(function(serie) {
      var res = serie.result();
      var out = {
        label : serie.get('label'),
        present_value : res[0][1],
        future_value : res[1][1]
      };
      return out;
    });
  }
});

var ChartList = Backbone.Collection.extend({
  model : Chart,

  initialize : function() {
    //_.bindAll(this, 'change_chart');
    //this.bind('add', this.change_chart);
  },

  change : function(chart) {
    var old_chart = this.first();
    if (old_chart !== undefined) {
      this.remove(old_chart);
    }
    this.add(chart);
  },

  load : function(chart_id) {
    if (this.first().get('id')+'' == chart_id + '') {
      // if chart_id == currently shown chart, skip.
      return;
    }
    var url = '/output_elements/'+chart_id+'.js?'+timestamp();
    $.getScript(url, function() {
      App.call_api('');
      // show/hide default chart button
      if(chart_id != charts.current_default_chart) {
        $("a.default_charts").show();
      } else {
        $("a.default_charts").hide();
      }
      // update chart information link
      $("#output_element_actions a.chart_info").attr("href", "/descriptions/charts/" + chart_id);
      // update the position of the output_element_actions
      $("#output_element_actions").removeClass();
      $("#output_element_actions").addClass(charts.first().get("type"));
    });
  }
});
window.charts = new ChartList();
<|endoftext|>


<commit_before>angular.module('soundboardApp').controller('DevController', ['$scope', '$firebaseArray', function($scope, $firebaseArray) {
  $scope.entry = {
    file: '',
    playCount: 0,
    title: '',
    new: true
  };

  var ref = new Firebase('https://bahuma-soundboard.firebaseio.com/soundboards/-KC7Il71Qx1_qMglteqp/entries');
  $scope.entries = new $firebaseArray(ref);

  $scope.saveEntry = function(entry) {
    $scope.entries.$add(entry);
  };
}]);
<commit_msg>Improved the add page<commit_after>angular.module('soundboardApp').controller('DevController', ['$scope', '$firebaseArray', function($scope, $firebaseArray) {
  var entryBoilerplate = {
    file: '',
    playCount: 0,
    title: '',
    new: true
  };

  $scope.entry = angular.copy(entryBoilerplate);

  var ref = new Firebase('https://bahuma-soundboard.firebaseio.com/soundboards/-KC7Il71Qx1_qMglteqp/entries');
  $scope.entries = new $firebaseArray(ref);

  $scope.saveEntry = function(entry) {
    $scope.entries.$add(entry);
    $scope.entry = angular.copy(entryBoilerplate);
  };
}]);
<|endoftext|>


<commit_before>import { css } from '@emotion/core';

import * as Constants from '~/constants/theme';
import { paragraph } from '~/components/base/typography';

export const globalTippy = css`
  div.tippy-tooltip {
    text-align: left;
    background: ${Constants.expoColors.black};
  }

  .tippy-popper[x-placement^='top'] .tippy-tooltip .tippy-roundarrow {
    fill: ${Constants.expoColors.black};
  }

  .tippy-tooltip.expo-theme .tippy-content {
    ${paragraph},
    color: ${Constants.colors.white};
    font-family: ${Constants.fonts.book};
    background: ${Constants.expoColors.black};
    padding: 8px;
  }

  .tippy-content a {
    color: #eee;
  }
`;
<commit_msg>[docs] Fix typo in tippy content styling (#10364)<commit_after>import { css } from '@emotion/core';

import { paragraph } from '~/components/base/typography';
import * as Constants from '~/constants/theme';

export const globalTippy = css`
  div.tippy-tooltip {
    text-align: left;
    background: ${Constants.expoColors.black};
  }

  .tippy-popper[x-placement^='top'] .tippy-tooltip .tippy-roundarrow {
    fill: ${Constants.expoColors.black};
  }

  .tippy-tooltip.expo-theme .tippy-content {
    ${paragraph};
    color: ${Constants.colors.white};
    font-family: ${Constants.fonts.book};
    background: ${Constants.expoColors.black};
    padding: 8px;
  }

  .tippy-content a {
    color: #eee;
  }
`;
<|endoftext|>


<commit_before>var map, layer, layers=[], mapOptions;

/*
This function craetes the map and layers.

Additional parameters and layers can be given to the
function.

map_div -- id of the element to create the map into
callback_function -- callback function the to be called after creation
    The callback_function will get the map as a parameter
*/
function create_map(map_div, callback_function) {
    var map_options_created = false;
    {% for p in layer_data %}
        {% if p.protocol = 'ARCcache' %}
            layer = new OpenLayers.Layer.ArcGISCache(
                "{{ p.name }}",
                "{{ p.source }}",
                { layerInfo: {{ p.layer_info|safe }} },
                {% if p.layer_type = 'BL'%}
                    {isBaseLayer: true}
                {% else %}
                    {isBaseLayer: false}
                {% endif %}
                );
            layers.push(layer);
            mapOptions = {
                maxExtent: layer.maxExtent,
                units: layer.units,
                resolutions: layer.resolutions,
                numZoomLevels: 10,
                tileSize: layer.tileSize,
                projection: layer.projection,
                units: layer.units,
                restrictedExtent: layer.maxExtent
                };
            map_options_created = true;
        {% else %}
        {% if p.protocol = 'ARCGIS' %}
            layer = new OpenLayers.Layer.ArcGIS93Rest(
                "{{ p.name }}",
                "{{ p.source }}",
                {layers: "show:{{ p.layers }}",
                format: "png24",
                transparent: true},
                {% if p.layer_type = 'BL'%}
                    {isBaseLayer: true}
                    {% else %}
                    {isBaseLayer: false}
                {% endif %}
                );
            layers.push(layer)
        {% else %}
        {% if p.protocol = 'OSM' %}
            layer = new OpenLayers.Layer.OSM(
                '{{ p.name }}'
                );
            layers.push(layer)
        {% else %}
        {% if p.protocol = 'WMS' %}
            layer = new OpenLayers.Layer.WMS(
                "{{ p.layer_name }}",
                "{{ p.source }}",
                {layers: "{{ p.layer }}"},
                {% if p.layer_type = 'BL'%}
                {isBaseLayer: true}
                {% else %}
                {isBaseLayer: false}
                {% endif %}
                );
            layers.push(layer)
        {% else %}
        {% if p.protocol = 'WFS' %}
            layer = new OpenLayers.Layer.Vector(
                "{{ p.name }}",
                {strategies: [new OpenLayers.Strategy.BBOX()],
                protocol: new OpenLayers.Protocol.WFS({
                    url:  "{{ p.source }}",
                    version: "1.1.0",
                    featureType: "{{ p.layer }}",
                    featureNS: "http://localhost:8080/geoserver/BaseTest",
                    geometryName: "the_geom"
                }),
                    visibility: false,
                    onFeatureInsert: function(event) {
                        count_order(event);
                    },
                    {% if p.layer_type = 'BL'%}
                    {isBaseLayer: true}
                    {% else %}
                    {isBaseLayer: false}
                    {% endif %}
                });
            layers.push(layer)
        {% endif %}
        {% endif %}
        {% endif %}
        {% endif %}
        {% endif %}
        if(map_options_created == false) {
            mapOptions = {
                projection:"EPSG:{{ map_data.projection }}",
                maxExtent: new OpenLayers.Bounds({{ map_data.max_extent }}),
                maxResolution: {{ map_data.max_resolution }},
                numZoomLevels: {{map_data.zoom_level }},
                tileSize: new OpenLayers.Size({{ map_data.tile_size }})
                };
        }
    {% endfor %}
    map = new OpenLayers.Map(map_div, mapOptions);
    map.addLayers(layers);

    if(callback_function !== undefined) {
        callback_function(map);
    }
}
<commit_msg>removed unneccessary if clause beacues arcgiscache is always a baselayer,, seems like it<commit_after>var map, layer, layers=[], mapOptions;

/*
This function craetes the map and layers.

Additional parameters and layers can be given to the
function.

map_div -- id of the element to create the map into
callback_function -- callback function the to be called after creation
    The callback_function will get the map as a parameter
*/
function create_map(map_div, callback_function) {
    var map_options_created = false;
    {% for p in layer_data %}
        {% if p.protocol = 'ARCcache' %}
            layer = new OpenLayers.Layer.ArcGISCache(
                "{{ p.name }}",
                "{{ p.source }}",
                { layerInfo: {{ p.layer_info|safe }} }
                );
            layers.push(layer);
            mapOptions = {
                maxExtent: layer.maxExtent,
                units: layer.units,
                resolutions: layer.resolutions,
                numZoomLevels: 10,
                tileSize: layer.tileSize,
                projection: layer.projection,
                units: layer.units,
                restrictedExtent: layer.maxExtent
                };
            map_options_created = true;
        {% else %}
        {% if p.protocol = 'ARCGIS' %}
            layer = new OpenLayers.Layer.ArcGIS93Rest(
                "{{ p.name }}",
                "{{ p.source }}",
                {layers: "show:{{ p.layers }}",
                format: "png24",
                transparent: true},
                {% if p.layer_type = 'BL'%}
                    {isBaseLayer: true}
                    {% else %}
                    {isBaseLayer: false}
                {% endif %}
                );
            layers.push(layer)
        {% else %}
        {% if p.protocol = 'OSM' %}
            layer = new OpenLayers.Layer.OSM(
                '{{ p.name }}'
                );
            layers.push(layer)
        {% else %}
        {% if p.protocol = 'WMS' %}
            layer = new OpenLayers.Layer.WMS(
                "{{ p.layer_name }}",
                "{{ p.source }}",
                {layers: "{{ p.layer }}"},
                {% if p.layer_type = 'BL'%}
                {isBaseLayer: true}
                {% else %}
                {isBaseLayer: false}
                {% endif %}
                );
            layers.push(layer)
        {% else %}
        {% if p.protocol = 'WFS' %}
            layer = new OpenLayers.Layer.Vector(
                "{{ p.name }}",
                {strategies: [new OpenLayers.Strategy.BBOX()],
                protocol: new OpenLayers.Protocol.WFS({
                    url:  "{{ p.source }}",
                    version: "1.1.0",
                    featureType: "{{ p.layer }}",
                    featureNS: "http://localhost:8080/geoserver/BaseTest",
                    geometryName: "the_geom"
                }),
                    visibility: false,
                    onFeatureInsert: function(event) {
                        count_order(event);
                    },
                    {% if p.layer_type = 'BL'%}
                    {isBaseLayer: true}
                    {% else %}
                    {isBaseLayer: false}
                    {% endif %}
                });
            layers.push(layer)
        {% endif %}
        {% endif %}
        {% endif %}
        {% endif %}
        {% endif %}
        if(map_options_created == false) {
            mapOptions = {
                projection:"EPSG:{{ map_data.projection }}",
                maxExtent: new OpenLayers.Bounds({{ map_data.max_extent }}),
                maxResolution: {{ map_data.max_resolution }},
                numZoomLevels: {{map_data.zoom_level }},
                tileSize: new OpenLayers.Size({{ map_data.tile_size }})
                };
        }
    {% endfor %}
    map = new OpenLayers.Map(map_div, mapOptions);
    map.addLayers(layers);

    if(callback_function !== undefined) {
        callback_function(map);
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var extend = require('xtend');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');

var errors = require('./errors');
var States = require('./reqres_states');
var Operations = require('./operations');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 100;
var CONNECTION_BASE_IDENTIFIER = 0;

function TChannelConnectionBase(channel, direction, socketRemoteAddr) {
    assert(!channel.destroyed, 'refuse to create connection for destroyed channel');

    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.timedOutEvent = self.defineEvent('timedOut');
    self.pingResponseEvent = self.defineEvent('pingResonse');

    self.closing = false;
    self.closeError = null;
    self.closeEvent = self.defineEvent('close');

    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.socketRemoteAddr = socketRemoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    self.ops = new Operations({
        timers: self.timers,
        logger: self.logger,
        random: self.random,
        initTimeout: self.channel.initTimeout,
        timeoutCheckInterval: self.options.timeoutCheckInterval,
        timeoutFuzz: self.options.timeoutFuzz,
        connectionStalePeriod: self.options.connectionStalePeriod,
        connection: self
    });

    self.guid = ++CONNECTION_BASE_IDENTIFIER;

    self.tracer = self.channel.tracer;
    self.ops.startTimeoutTimer();
}
inherits(TChannelConnectionBase, EventEmitter);

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};

    assert(self.remoteName, 'cannot make request unless identified');
    options.remoteAddr = self.remoteName;

    options.channel = self.channel;

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // assert(!self.requests.out[id], 'duplicate frame id in flight');
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutRequest(options);

    return self.ops.addOutReq(req);
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;

    self.ops.addInReq(req);

    req.remoteAddr = self.remoteName;
    req.errorEvent.on(onReqError);

    process.nextTick(runHandler);

    function onReqError(err) {
        self.onReqError(req, err);
    }

    function runHandler() {
        self.runHandler(req);
    }
};

TChannelConnectionBase.prototype.onReqError = function onReqError(req, err) {
    var self = this;
    if (!req.res) self.buildResponse(req, {});
    if (err.type === 'tchannel.timeout' ||
        err.type === 'tchannel.request.timeout'
    ) {
        req.res.sendError('Timeout', err.message);
    } else {
        var errName = err.name || err.constructor.name;
        req.res.sendError('UnexpectedError', errName + ': ' + err.message);
    }
};

TChannelConnectionBase.prototype.runHandler = function runHandler(req) {
    var self = this;

    self.channel.emitFastStat(self.channel.buildStat(
        'inbound.calls.recvd',
        'counter',
        1,
        new InboundCallsRecvdTags(
            req.headers.cn,
            req.serviceName,
            req.endpoint
        )
    ));

    // self.channel.inboundCallsRecvdStat.increment(1, {
    //     'calling-service': req.headers.cn,
    //     'service': req.serviceName,
    //     'endpoint': String(req.arg1)
    // });

    self.channel.handler.handleRequest(req, buildResponse);
    function buildResponse(options) {
        return self.buildResponse(req, options || {});
    }
};

function InboundCallsRecvdTags(cn, serviceName, endpoint) {
    var self = this;

    self.app = null;
    self.host = null;
    self.cluster = null;
    self.version = null;

    self.callingService = cn || '';
    self.service = serviceName;
    self.endpoint = endpoint;
}

TChannelConnectionBase.prototype.buildResponse =
function buildResponse(req, options) {
    var self = this;

    if (req.res && req.res.state !== States.Initial) {
        self.errorEvent.emit(self, errors.ResponseAlreadyStarted({
            state: req.res.state,
            reason: 'buildResponse called twice',
            codeString: req.res.codeString,
            responseMessage: req.res.message
        }));
    }

    options.channel = self.channel;
    options.inreq = req;

    // TODO give this options a well defined type
    req.res = self.buildOutResponse(req, options);

    req.res.errorEvent.on(onError);
    req.res.finishEvent.on(opDone);

    if (!req.forwardTrace) {
        self.captureResponseSpans(req.res);
    }

    return req.res;

    function opDone() {
        self.onReqDone(req);
    }

    function onError(err) {
        self.onResponseError(err, req);
    }
};

TChannelConnectionBase.prototype.captureResponseSpans =
function captureResponseSpans(res) {
    var self = this;

    res.spanEvent.on(handleSpanFromRes);

    function handleSpanFromRes(span) {
        self.handleSpanFromRes(span);
    }
}

function isStringOrBuffer(x) {
    return typeof x === 'string' || Buffer.isBuffer(x);
}

TChannelConnectionBase.prototype.handleSpanFromRes =
function handleSpanFromRes(span) {
    var self = this;

    self.channel.tracer.report(span);
};

TChannelConnectionBase.prototype.onResponseError =
function onResponseError(err, req) {
    var self = this;

    var loggingOptions = {
        error: err,
        arg1: String(req.arg1),
        ok: req.res.ok,
        type: req.res.type,
        state: req.res.state === States.Done ? 'Done' :
            req.res.state === States.Error ? 'Error' :
            'Unknown'
    };

    if (req.res.state === States.Done) {
        var arg2 = isStringOrBuffer(req.res.arg2) ?
            req.res.arg2 : 'streaming';
        var arg3 = isStringOrBuffer(req.res.arg3) ?
            req.res.arg3 : 'streaming';

        loggingOptions.bufArg2 = arg2.slice(0, 50);
        loggingOptions.arg2 = String(arg2).slice(0, 50);
        loggingOptions.bufArg3 = arg3.slice(0, 50);
        loggingOptions.arg3 = String(arg3).slice(0, 50);
    } else if (req.res.state === States.Error) {
        loggingOptions.codeString = req.res.codeString;
        loggingOptions.errMessage = req.res.message;
    }

    if ((err.type === 'tchannel.response-already-started' ||
        err.type === 'tchannel.response-already-done') &&
        req.timedOut
    ) {
        self.logger.info(
            'error for timed out outgoing response', loggingOptions
        );
    } else {
        self.logger.error(
            'outgoing response has an error', loggingOptions
        );
    }
};

TChannelConnectionBase.prototype.onReqDone = function onReqDone(req) {
    var self = this;

    var inreq = self.ops.popInReq(req.id);

    // incoming req that timed out are already cleaned up
    if (inreq !== req && !req.timedOut) {
        self.logger.warn('mismatched onReqDone callback', {
            hostPort: self.channel.hostPort,
            hasInReq: inreq !== undefined,
            id: req.id
        });
    }
};

module.exports = TChannelConnectionBase;
<commit_msg>lint: comply with styles<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');

var errors = require('./errors');
var States = require('./reqres_states');
var Operations = require('./operations');

var DEFAULT_OUTGOING_REQ_TIMEOUT = 100;
var CONNECTION_BASE_IDENTIFIER = 0;

function TChannelConnectionBase(channel, direction, socketRemoteAddr) {
    assert(!channel.destroyed, 'refuse to create connection for destroyed channel');

    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.timedOutEvent = self.defineEvent('timedOut');
    self.pingResponseEvent = self.defineEvent('pingResonse');

    self.closing = false;
    self.closeError = null;
    self.closeEvent = self.defineEvent('close');

    self.channel = channel;
    self.options = self.channel.options;
    self.logger = channel.logger;
    self.random = channel.random;
    self.timers = channel.timers;
    self.direction = direction;
    self.socketRemoteAddr = socketRemoteAddr;
    self.timer = null;
    self.remoteName = null; // filled in by identify message

    self.ops = new Operations({
        timers: self.timers,
        logger: self.logger,
        random: self.random,
        initTimeout: self.channel.initTimeout,
        timeoutCheckInterval: self.options.timeoutCheckInterval,
        timeoutFuzz: self.options.timeoutFuzz,
        connectionStalePeriod: self.options.connectionStalePeriod,
        connection: self
    });

    self.guid = ++CONNECTION_BASE_IDENTIFIER;

    self.tracer = self.channel.tracer;
    self.ops.startTimeoutTimer();
}
inherits(TChannelConnectionBase, EventEmitter);

// create a request
TChannelConnectionBase.prototype.request = function connBaseRequest(options) {
    var self = this;
    if (!options) options = {};

    assert(self.remoteName, 'cannot make request unless identified');
    options.remoteAddr = self.remoteName;

    options.channel = self.channel;

    // TODO: use this to protect against >4Mi outstanding messages edge case
    // (e.g. zombie operation bug, incredible throughput, or simply very long
    // timeout
    // assert(!self.requests.out[id], 'duplicate frame id in flight');
    // TODO: provide some sort of channel default for "service"
    // TODO: generate tracing if empty?
    // TODO: refactor callers
    options.checksumType = options.checksum;

    // TODO: better default, support for dynamic
    options.ttl = options.timeout || DEFAULT_OUTGOING_REQ_TIMEOUT;
    options.tracer = self.tracer;
    var req = self.buildOutRequest(options);

    return self.ops.addOutReq(req);
};

TChannelConnectionBase.prototype.handleCallRequest = function handleCallRequest(req) {
    var self = this;

    self.ops.addInReq(req);

    req.remoteAddr = self.remoteName;
    req.errorEvent.on(onReqError);

    process.nextTick(runHandler);

    function onReqError(err) {
        self.onReqError(req, err);
    }

    function runHandler() {
        self.runHandler(req);
    }
};

TChannelConnectionBase.prototype.onReqError = function onReqError(req, err) {
    var self = this;
    if (!req.res) self.buildResponse(req, {});
    if (err.type === 'tchannel.timeout' ||
        err.type === 'tchannel.request.timeout'
    ) {
        req.res.sendError('Timeout', err.message);
    } else {
        var errName = err.name || err.constructor.name;
        req.res.sendError('UnexpectedError', errName + ': ' + err.message);
    }
};

TChannelConnectionBase.prototype.runHandler = function runHandler(req) {
    var self = this;

    self.channel.emitFastStat(self.channel.buildStat(
        'inbound.calls.recvd',
        'counter',
        1,
        new InboundCallsRecvdTags(
            req.headers.cn,
            req.serviceName,
            req.endpoint
        )
    ));

    // self.channel.inboundCallsRecvdStat.increment(1, {
    //     'calling-service': req.headers.cn,
    //     'service': req.serviceName,
    //     'endpoint': String(req.arg1)
    // });

    self.channel.handler.handleRequest(req, buildResponse);
    function buildResponse(options) {
        return self.buildResponse(req, options || {});
    }
};

function InboundCallsRecvdTags(cn, serviceName, endpoint) {
    var self = this;

    self.app = null;
    self.host = null;
    self.cluster = null;
    self.version = null;

    self.callingService = cn || '';
    self.service = serviceName;
    self.endpoint = endpoint;
}

TChannelConnectionBase.prototype.buildResponse =
function buildResponse(req, options) {
    var self = this;

    if (req.res && req.res.state !== States.Initial) {
        self.errorEvent.emit(self, errors.ResponseAlreadyStarted({
            state: req.res.state,
            reason: 'buildResponse called twice',
            codeString: req.res.codeString,
            responseMessage: req.res.message
        }));
    }

    options.channel = self.channel;
    options.inreq = req;

    // TODO give this options a well defined type
    req.res = self.buildOutResponse(req, options);

    req.res.errorEvent.on(onError);
    req.res.finishEvent.on(opDone);

    if (!req.forwardTrace) {
        self.captureResponseSpans(req.res);
    }

    return req.res;

    function opDone() {
        self.onReqDone(req);
    }

    function onError(err) {
        self.onResponseError(err, req);
    }
};

TChannelConnectionBase.prototype.captureResponseSpans =
function captureResponseSpans(res) {
    var self = this;

    res.spanEvent.on(handleSpanFromRes);

    function handleSpanFromRes(span) {
        self.handleSpanFromRes(span);
    }
};

function isStringOrBuffer(x) {
    return typeof x === 'string' || Buffer.isBuffer(x);
}

TChannelConnectionBase.prototype.handleSpanFromRes =
function handleSpanFromRes(span) {
    var self = this;

    self.channel.tracer.report(span);
};

TChannelConnectionBase.prototype.onResponseError =
function onResponseError(err, req) {
    var self = this;

    var loggingOptions = {
        error: err,
        arg1: String(req.arg1),
        ok: req.res.ok,
        type: req.res.type,
        state: req.res.state === States.Done ? 'Done' :
            req.res.state === States.Error ? 'Error' :
            'Unknown'
    };

    if (req.res.state === States.Done) {
        var arg2 = isStringOrBuffer(req.res.arg2) ?
            req.res.arg2 : 'streaming';
        var arg3 = isStringOrBuffer(req.res.arg3) ?
            req.res.arg3 : 'streaming';

        loggingOptions.bufArg2 = arg2.slice(0, 50);
        loggingOptions.arg2 = String(arg2).slice(0, 50);
        loggingOptions.bufArg3 = arg3.slice(0, 50);
        loggingOptions.arg3 = String(arg3).slice(0, 50);
    } else if (req.res.state === States.Error) {
        loggingOptions.codeString = req.res.codeString;
        loggingOptions.errMessage = req.res.message;
    }

    if ((err.type === 'tchannel.response-already-started' ||
        err.type === 'tchannel.response-already-done') &&
        req.timedOut
    ) {
        self.logger.info(
            'error for timed out outgoing response', loggingOptions
        );
    } else {
        self.logger.error(
            'outgoing response has an error', loggingOptions
        );
    }
};

TChannelConnectionBase.prototype.onReqDone = function onReqDone(req) {
    var self = this;

    var inreq = self.ops.popInReq(req.id);

    // incoming req that timed out are already cleaned up
    if (inreq !== req && !req.timedOut) {
        self.logger.warn('mismatched onReqDone callback', {
            hostPort: self.channel.hostPort,
            hasInReq: inreq !== undefined,
            id: req.id
        });
    }
};

module.exports = TChannelConnectionBase;
<|endoftext|>


<commit_before>// === Import Necessary Functionality ===
var fileSystem = require('fs');
var parser = require('./parser.js');

// === Creat Necessary Variables ===
var games = {};

// ----------------------------\
// === Main Function ==================================================================================================
// ----------------------------/
exports.input = function(input, gameID){
	var command = parser.parse(input);
	var game = games[gameID];
	if(game){
		var gameFunctions = game.gameFunctions;
		game = game.gameData;
		++game.commandCounter;
		console.log(gameID + ': ' + game.commandCounter);
		try {
			try {
				return eval('gameFunctions.'+command.action+'(game,command)');
			} catch(error) {
				return eval('actions.'+command.action+'(game,command)');
			}
		} catch(error){
			try {
				return interact(game, command.action, command.subject);
			} catch (error2) {
				return "I don't know how to do that.";
			}
		}
	} else {
		console.log(gameID + ': no game');
		if(command.action === 'load'){
			return loadCartridge(gameID, command.subject);
		} else {
			return listCartridges();
		}
	}
};

// ----------------------------\
// === Game Setup Functions ===========================================================================================
// ----------------------------/
function listCartridges(){
	var cartridges = fileSystem.readdirSync('./cartridges/');
	var cartridgesFormated = 'Avaliable Games: \n';
	for(var i = 0; i < cartridges.length; i++){
		cartridgesFormated = cartridgesFormated.concat(cartridges[i].substr(0,cartridges[i].lastIndexOf('.')));
		if(i < cartridges.length-1){
			cartridgesFormated = cartridgesFormated.concat('\n');
		}
	}
	return cartridgesFormated;
}

function loadCartridge(gameID, gameName){
	if (!gameName){
		return "Specify game cartridge to load.";
	}
	try {
		var file = eval('require("../cartridges/'+gameName+'.js")');
		games[gameID] = {gameData: file.gameData, gameFunctions: file.gameFunctions};
		return games[gameID].gameData.introText + '\n' + getLocationDescription(games[gameID].gameData);
	} catch(error){
		return "Could not load " + gameName;
	}
}

// ----------------------------\
// === Game Commands ==================================================================================================
// ----------------------------/
var actions = {

	drop : function(game, command){
		if(!command.subject){
			return 'What do you want to drop?';
		}
		try{
			return interact(game, 'drop', command.subject);
		} catch(error) {
			try {
				var currentLocation = getCurrentLocation(game);
				moveItem(command.subject, game.player.inventory, currentLocation.items);
				var item = getItem(currentLocation.items, command.subject);
				item.hidden = false;
				return command.subject + ' dropped';
			} catch(error2){
				return 'You do not have a ' + command.subject + ' to drop.';
			}
		}
	},

	go : function(game, command){
		if(!command.subject){
			return 'Where do you want to go?';
		}
		var exits = getCurrentLocation(game);
		// TODO Finish Function
	},

	inventory : function(game, command){
		var inventoryList = 'Your inventory contains:';
		for (var item in game.player.inventory){
			var itemObject = game.player.inventory[item];
			var itemName = itemObject.displayName;
			if(itemObject.quantity > 1){
				itemName = itemName.concat(' x'+itemObject.quantity);
			}
			inventoryList = inventoryList.concat('\n'+itemName);
		}
		if (inventoryList === 'Your inventory contains:'){
			return 'Your inventory is empty.';
		} else {
			return inventoryList;
		}
	},

	look : function(game, command){
		if(!command.subject){
			return getLocationDescription(game);
		}
		try {
			return getItem(game.player.inventory, command.subject).description;
		} catch(error) {
			try {
				return interact(game, 'look', command.subject);
			} catch(error2) {
				return 'There is nothing important about the '+command.subject+'.';
			}
		}
	},

	take : function(game, command){
		if(!command.subject){
			return 'What do you want to take?';
		}
		try{
			return interact(game, 'take', command.subject);
		} catch(error) {
			try {
				moveItem(command.subject, getCurrentLocation(game).items, game.player.inventory);
				return command.subject + ' taken';
			} catch(error2){
				return 'Best just to leave the ' + command.subject + ' as it is.';
			}
		}
	},

	use : function(game, command){
		//TODO Finish Function
		return "TODO";
	}
};

// ----------------------------\
// === Helper Functions ===============================================================================================
// ----------------------------/
function clone(objectToBeCloned){
	return JSON.parse(JSON.stringify(objectToBeCloned));
}

function getCurrentLocation(game){
	return game.map[game.player.currentLocation];
}

function getLocationDescription(game){
	var currentLocation = getCurrentLocation(game);
	var description;
	if(currentLocation.firstVisit){
		description = currentLocation.description;
		for(var item in currentLocation.items){
			var itemObject = currentLocation.items[item];
			if(!itemObject.hidden){
				if(itemObject.quantity > 1){
					description = description.concat(' There are '+itemObject.quantity+' '+itemObject.displayName+'s here.');
				} else {
					description = description.concat(' There is a '+itemObject.displayName+' here.');
				}
			}
		}
		var exitString = ' Exits are';
		var exitCount = 1;
		for(var exit in currentLocation.exits){
			var exitObject = currentLocation.exits[exit];
			switch (exitCount){
				case 1 :
					exitString = exitString.concat(' '+exitObject.displayName);
					break;
				default :
					exitString = exitString.concat(', '+exitObject.displayName);
			}
			++exitCount;
		}
		description = description.concat(exitString.concat('.'));
	} else {
		description = currentLocation.displayName;
	}
	return description;
}

function getItem(itemLocation, itemName){
	return itemLocation[getItemName(itemLocation, itemName)];
}

function getItemName(itemLocation, itemName){
	if(itemLocation[itemName] !== undefined) {
		return itemName;
	} else {
		for(var item in itemLocation){
			if(itemLocation[item].displayName.toLowerCase() === itemName){
				return item;
			}
		}
	}
}

function interact(game, interaction, subject){
	try {
		var interactionResult = eval('game.map.'+game.player.currentLocation+'.interactables.'+subject+'.'+interaction+'();');
		if(interactionResult === undefined){
			throw 'noFunction';
		}
		return interactionResult;
	} catch(error) {
		var interactionResult = getCurrentLocation(game).interactables[subject][interaction];
		if(interactionResult === undefined || typeof interactionResult === 'function'){
			throw 'noString';
		}
		return interactionResult;
	}
}

function moveItem(itemName, startLocation, endLocation){
	var itemName = getItemName(startLocation, itemName);
	var itemAtOrigin = getItem(startLocation, itemName);
	if(itemAtOrigin === undefined){
		throw 'itemDoesNotExist';
	}
	var itemAtDestination = getItem(endLocation, itemName);
	if(itemAtDestination === undefined) {
		endLocation[itemName] = clone(itemAtOrigin);
		endLocation[itemName].quantity = 1;
	} else {
		++endLocation[itemName].quantity;
	}
	if (itemAtOrigin.hasOwnProperty('quantity')){
		--itemAtOrigin.quantity;
		if(itemAtOrigin.quantity === 0){
			delete startLocation[itemName];
		}
	}
	return 'success';
}<commit_msg>Removed Extraneous Console Logging<commit_after>// === Import Necessary Functionality ===
var fileSystem = require('fs');
var parser = require('./parser.js');

// === Creat Necessary Variables ===
var games = {};

// ----------------------------\
// === Main Function ==================================================================================================
// ----------------------------/
exports.input = function(input, gameID){
	var command = parser.parse(input);
	var game = games[gameID];
	if(game){
		var gameFunctions = game.gameFunctions;
		game = game.gameData;
		++game.commandCounter;
		console.log(gameID + ': ' + game.commandCounter);
		try {
			try {
				return eval('gameFunctions.'+command.action+'(game,command)');
			} catch(error) {
				return eval('actions.'+command.action+'(game,command)');
			}
		} catch(error){
			try {
				return interact(game, command.action, command.subject);
			} catch (error2) {
				return "I don't know how to do that.";
			}
		}
	} else {
		console.log(gameID + ': no game');
		if(command.action === 'load'){
			return loadCartridge(gameID, command.subject);
		} else {
			return listCartridges();
		}
	}
};

// ----------------------------\
// === Game Setup Functions ===========================================================================================
// ----------------------------/
function listCartridges(){
	var cartridges = fileSystem.readdirSync('./cartridges/');
	var cartridgesFormated = 'Avaliable Games: \n';
	for(var i = 0; i < cartridges.length; i++){
		cartridgesFormated = cartridgesFormated.concat(cartridges[i].substr(0,cartridges[i].lastIndexOf('.')));
		if(i < cartridges.length-1){
			cartridgesFormated = cartridgesFormated.concat('\n');
		}
	}
	return cartridgesFormated;
}

function loadCartridge(gameID, gameName){
	if (!gameName){
		return "Specify game cartridge to load.";
	}
	try {
		var file = eval('require("../cartridges/'+gameName+'.js")');
		games[gameID] = {gameData: file.gameData, gameFunctions: file.gameFunctions};
		return games[gameID].gameData.introText + '\n' + getLocationDescription(games[gameID].gameData);
	} catch(error){
		return "Could not load " + gameName;
	}
}

// ----------------------------\
// === Game Commands ==================================================================================================
// ----------------------------/
var actions = {

	drop : function(game, command){
		if(!command.subject){
			return 'What do you want to drop?';
		}
		try{
			return interact(game, 'drop', command.subject);
		} catch(error) {
			try {
				var currentLocation = getCurrentLocation(game);
				moveItem(command.subject, game.player.inventory, currentLocation.items);
				var item = getItem(currentLocation.items, command.subject);
				item.hidden = false;
				return command.subject + ' dropped';
			} catch(error2){
				return 'You do not have a ' + command.subject + ' to drop.';
			}
		}
	},

	go : function(game, command){
		if(!command.subject){
			return 'Where do you want to go?';
		}
		var exits = getCurrentLocation(game);
		// TODO Finish Function
	},

	inventory : function(game, command){
		var inventoryList = 'Your inventory contains:';
		for (var item in game.player.inventory){
			var itemObject = game.player.inventory[item];
			var itemName = itemObject.displayName;
			if(itemObject.quantity > 1){
				itemName = itemName.concat(' x'+itemObject.quantity);
			}
			inventoryList = inventoryList.concat('\n'+itemName);
		}
		if (inventoryList === 'Your inventory contains:'){
			return 'Your inventory is empty.';
		} else {
			return inventoryList;
		}
	},

	look : function(game, command){
		if(!command.subject){
			return getLocationDescription(game);
		}
		try {
			return getItem(game.player.inventory, command.subject).description;
		} catch(error) {
			try {
				return interact(game, 'look', command.subject);
			} catch(error2) {
				return 'There is nothing important about the '+command.subject+'.';
			}
		}
	},

	take : function(game, command){
		if(!command.subject){
			return 'What do you want to take?';
		}
		try{
			return interact(game, 'take', command.subject);
		} catch(error) {
			try {
				moveItem(command.subject, getCurrentLocation(game).items, game.player.inventory);
				return command.subject + ' taken';
			} catch(error2){
				return 'Best just to leave the ' + command.subject + ' as it is.';
			}
		}
	},

	use : function(game, command){
		//TODO Finish Function
		return "TODO";
	}
};

// ----------------------------\
// === Helper Functions ===============================================================================================
// ----------------------------/
function clone(objectToBeCloned){
	return JSON.parse(JSON.stringify(objectToBeCloned));
}

function getCurrentLocation(game){
	return game.map[game.player.currentLocation];
}

function getLocationDescription(game){
	var currentLocation = getCurrentLocation(game);
	var description;
	if(currentLocation.firstVisit){
		description = currentLocation.description;
		for(var item in currentLocation.items){
			var itemObject = currentLocation.items[item];
			if(!itemObject.hidden){
				if(itemObject.quantity > 1){
					description = description.concat(' There are '+itemObject.quantity+' '+itemObject.displayName+'s here.');
				} else {
					description = description.concat(' There is a '+itemObject.displayName+' here.');
				}
			}
		}
		var exitString = ' Exits are';
		var exitCount = 1;
		for(var exit in currentLocation.exits){
			var exitObject = currentLocation.exits[exit];
			switch (exitCount){
				case 1 :
					exitString = exitString.concat(' '+exitObject.displayName);
					break;
				default :
					exitString = exitString.concat(', '+exitObject.displayName);
			}
			++exitCount;
		}
		description = description.concat(exitString.concat('.'));
	} else {
		description = currentLocation.displayName;
	}
	return description;
}

function getItem(itemLocation, itemName){
	return itemLocation[getItemName(itemLocation, itemName)];
}

function getItemName(itemLocation, itemName){
	if(itemLocation[itemName] !== undefined) {
		return itemName;
	} else {
		for(var item in itemLocation){
			if(itemLocation[item].displayName.toLowerCase() === itemName){
				return item;
			}
		}
	}
}

function interact(game, interaction, subject){
	try {
		var interactionResult = eval('game.map.'+game.player.currentLocation+'.interactables.'+subject+'.'+interaction+'();');
		if(interactionResult === undefined){
			throw 'noFunction';
		}
		return interactionResult;
	} catch(error) {
		console.log(error);
		var interactionResult = getCurrentLocation(game).interactables[subject][interaction];
		if(interactionResult === undefined || typeof interactionResult === 'function'){
			throw 'noString';
		}
		return interactionResult;
	}
}

function moveItem(itemName, startLocation, endLocation){
	var itemName = getItemName(startLocation, itemName);
	var itemAtOrigin = getItem(startLocation, itemName);
	if(itemAtOrigin === undefined){
		throw 'itemDoesNotExist';
	}
	var itemAtDestination = getItem(endLocation, itemName);
	if(itemAtDestination === undefined) {
		endLocation[itemName] = clone(itemAtOrigin);
		endLocation[itemName].quantity = 1;
	} else {
		++endLocation[itemName].quantity;
	}
	if (itemAtOrigin.hasOwnProperty('quantity')){
		--itemAtOrigin.quantity;
		if(itemAtOrigin.quantity === 0){
			delete startLocation[itemName];
		}
	}
	return 'success';
}<|endoftext|>