<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.
 *
 */

define(function (require, exports) {
    "use strict";

    var Promise = require("bluebird"),
        _ = require("lodash");

    var photoshopEvent = require("adapter/lib/photoshopEvent"),
        descriptor = require("adapter/ps/descriptor"),
        documentLib = require("adapter/lib/document"),
        layerLib = require("adapter/lib/layer"),
        ui = require("./ui"),
        events = require("../events"),
        locks = require("js/locks");

    /**
     * Get an array of layer descriptors for the given document descriptor.
     *
     * @private
     * @param {object} doc Document descriptor
     * @return {Promise.<Array.<object>>} Resolves with an array of layer descriptors
     */
    var _getLayersForDocument = function (doc) {
        var layerCount = doc.numberOfLayers,
            startIndex = (doc.hasBackgroundLayer ? 0 : 1),
            layerGets = _.range(layerCount, startIndex - 1, -1).map(function (i) {
                var layerReference = [
                    documentLib.referenceBy.id(doc.documentID),
                    layerLib.referenceBy.index(i)
                ];
                return descriptor.get(layerReference);
            });

        return Promise.all(layerGets);
    };

    var createNewCommand = function () {
        var docSettings = {
            width: 480,
            height: 480,
            resolution: 300,
            fill: "white",
            depth: 8,
            colorMode: "RGBColor",
            profile: "none",
            pixelAspectRation: 1
        };

        return descriptor.playObject(documentLib.create(docSettings))
            .bind(this)
            .then(function (result) {
                this.transfer(allocateDocument, result.documentID);
            });
    };

    /**
     * Completely reset all document and layer state. This is a heavy operation
     * that should only be called in an emergency!
     *
     * @private
     * @return {Promise}
     */
    var onResetCommand = function () {
        return descriptor.getProperty("application", "numberOfDocuments")
            .bind(this)
            .then(function (docCount) {
                var payload = {};
                if (docCount === 0) {
                    payload.selectedDocumentID = null;
                    payload.documents = [];
                    this.dispatch(events.documents.RESET_DOCUMENTS, payload);
                    return;
                }

                var openDocumentPromises = _.range(1, docCount + 1)
                    .map(function (index) {
                        var indexRef = documentLib.referenceBy.index(index);
                        return descriptor.get(indexRef)
                            .then(function (doc) {
                                return _getLayersForDocument(doc)
                                    .bind(this)
                                    .then(function (layers) {
                                        return {
                                            document: doc,
                                            layers: layers
                                        };
                                    });
                            });
                    }),
                    openDocumentsPromise = Promise.all(openDocumentPromises);

                var currentRef = documentLib.referenceBy.current,
                    currentDocumentIDPromise = descriptor.getProperty(currentRef, "documentID");

                return Promise.join(currentDocumentIDPromise, openDocumentsPromise,
                    function (currentDocumentID, openDocuments) {
                        payload.selectedDocumentID = currentDocumentID;
                        payload.documents = openDocuments;
                        this.dispatch(events.documents.RESET_DOCUMENTS, payload);
                    }.bind(this));
            });
    };

    /**
     * Initialize document and layer state, emitting DOCUMENT_UPDATED and
     * CURRENT_DOCUMENT_UPDATED events for the open documents. This is different
     * from resetDocumentsCommand in two ways: 1) the emitted events are interpreted
     * by the stores as being additive (i.e., each new DOCUMENT_UPDATED event is
     * treated as indication that there is another document open); 2) these events
     * are emitted individually, and in particular the event for the current document
     * is emitted first. This is a performance optimization to allow the UI to be
     * rendered for the active document before continuing to build models for the
     * other documents.
     *
     * @return {Promise}
     */
    var initDocumentsCommand = function () {
        return descriptor.getProperty("application", "numberOfDocuments")
            .bind(this)
            .then(function (docCount) {
                if (docCount === 0) {
                    return;
                }

                var currentRef = documentLib.referenceBy.current;
                return descriptor.get(currentRef)
                    .bind(this)
                    .then(function (currentDoc) {
                        var currentDocLayersPromise = _getLayersForDocument(currentDoc)
                            .bind(this)
                            .then(function (layers) {
                                var payload = {
                                    document: currentDoc,
                                    layers: layers
                                };

                                this.dispatch(events.documents.CURRENT_DOCUMENT_UPDATED, payload);
                            });

                        var otherDocPromises = _.range(1, docCount + 1)
                            .filter(function (index) {
                                return index !== currentDoc.itemIndex;
                            })
                            .map(function (index) {
                                var indexRef = documentLib.referenceBy.index(index);
                                return descriptor.get(indexRef)
                                    .bind(this)
                                    .then(function (doc) {
                                        return _getLayersForDocument(doc)
                                            .bind(this)
                                            .then(function (layers) {
                                                var payload = {
                                                    document: doc,
                                                    layers: layers
                                                };

                                                this.dispatch(events.documents.DOCUMENT_UPDATED, payload);
                                            });
                                    });
                            }, this),
                            otherDocsPromise = Promise.all(otherDocPromises);

                        return Promise.join(currentDocLayersPromise, otherDocsPromise);
                    });
            });
    };

    /**
     * Fetch the ID of the currently selected document, or null if there is none.
     *
     * @private
     * @return {Promise.<?number>}
     */
    var _getSelectedDocumentID = function () {
        var currentRef = documentLib.referenceBy.current;
        return descriptor.getProperty(currentRef, "documentID")
            .catch(function () {
                return null;
            });
    };

    /**
     * Dispose of a previously opened document.
     *
     * @private
     * @param {!number} documentID
     * @return {Promise}
     */
    var disposeDocumentCommand = function (documentID) {
        var disposePromise = _getSelectedDocumentID()
            .bind(this)
            .then(function (currentDocumentID) {
                var payload = {
                    documentID: documentID,
                    selectedDocumentID: currentDocumentID
                };

                this.dispatch(events.documents.CLOSE_DOCUMENT, payload);
            });

        var transformPromise = this.transfer(ui.updateTransform);

        return Promise.join(disposePromise, transformPromise);
    };

    /**
     * Allocate a newly opened document. Emits DOCUMENT_UPDATED and a SELECT_DOCUMENT
     * events.
     *
     * @private
     * @param {!number} documentID
     * @return {Promise}
     */
    var allocateDocumentCommand = function (documentID) {
        var updatePromise = this.transfer(updateDocument, documentID),
            selectedDocumentPromise = _getSelectedDocumentID(),
            allocatePromise = Promise.join(selectedDocumentPromise, updatePromise,
                function (currentDocumentID) {
                    var payload = {
                        selectedDocumentID: currentDocumentID
                    };

                    this.dispatch(events.documents.SELECT_DOCUMENT, payload);
                }.bind(this));

        var transformPromise = this.transfer(ui.updateTransform);

        return Promise.join(allocatePromise, transformPromise);
    };

    /**
     * Update the document and layer state for the given document ID. Emits a
     * single DOCUMENT_UPDATED event.
     *
     * @param {number} id Document ID
     * @return {Promise}
     */
    var updateDocumentCommand = function (id) {
        var docRef = documentLib.referenceBy.id(id);
        return descriptor.get(docRef)
            .bind(this)
            .then(function (doc) {
                return _getLayersForDocument(doc)
                    .bind(this)
                    .then(function (layerArray) {
                        var payload = {
                            document: doc,
                            layers: layerArray
                        };
                        this.dispatch(events.documents.DOCUMENT_UPDATED, payload);
                    });
            });
    };

    /**
     * Update the document and layer state for the currently active document ID.
     * Emits a single CURRENT_DOCUMENT_UPDATED event.
     *
     * @return {Promise}
     */
    var updateCurrentDocumentCommand = function () {
        var currentRef = documentLib.referenceBy.current;
        return descriptor.get(currentRef)
            .bind(this)
            .then(function (doc) {
                return _getLayersForDocument(doc)
                    .bind(this)
                    .then(function (layers) {
                        var payload = {
                            document: doc,
                            layers: layers
                        };
                        this.dispatch(events.documents.CURRENT_DOCUMENT_UPDATED, payload);
                    });
            });
    };

    /**
     * Activate the given already-open document
     *
     * @param {Document} document
     * @return {Promise}
     */
    var selectDocumentCommand = function (document) {
        return descriptor.playObject(documentLib.select(documentLib.referenceBy.id(document.id)))
            .bind(this)
            .then(function () {
                var payload = {
                    selectedDocumentID: document.id
                };

                this.dispatch(events.documents.SELECT_DOCUMENT, payload);
            });
    };

    /**
     * Activate the next open document in the document index
     *
     * @return {Promise}
     */
    var selectNextDocumentCommand = function () {
        var applicationStore = this.flux.store("application"),
            nextDocument = applicationStore.getNextDocument();

        if (!nextDocument) {
            return Promise.resolve();
        }

        return this.transfer(selectDocument, nextDocument);
    };

    /**
     * Activate the previous open document in the document index
     *
     * @return {Promise}
     */
    var selectPreviousDocumentCommand = function () {
        var applicationStore = this.flux.store("application"),
            previousDocument = applicationStore.getPreviousDocument();

        if (!previousDocument) {
            return Promise.resolve();
        }

        return this.transfer(selectDocument, previousDocument);
    };

    /**
     * Register event listeners for active and open document change events, and
     * initialize the active and open document lists.
     *
     * @return {Promise}
     */
    var onStartupCommand = function () {
        var applicationStore = this.flux.store("application");

        descriptor.addListener("make", function (event) {
            var target = photoshopEvent.targetOf(event),
                currentDocument;

            switch (target) {
            case "document":
                // A new document was created
                if (typeof event.documentID === "number") {
                    this.flux.actions.documents.allocateDocument(event.documentID);
                } else {
                    this.flux.actions.documents.resetDocuments();
                }

                break;
            case "layer":
            case "contentLayer":
            case "textLayer":
                // A layer was added
                currentDocument = applicationStore.getCurrentDocument();
                this.flux.actions.documents.updateDocument(currentDocument.id);
                break;
            }
        }.bind(this));

        descriptor.addListener("open", function (event) {
            // A new document was opened
            if (typeof event.documentID === "number") {
                this.flux.actions.documents.allocateDocument(event.documentID);
            } else {
                this.flux.actions.documents.resetDocuments();
            }
        }.bind(this));

        descriptor.addListener("close", function (event) {
            // An open document was closed
            if (typeof event.documentID === "number") {
                this.flux.actions.documents.disposeDocument(event.documentID);
            } else {
                this.flux.actions.documents.resetDocuments();
            }
        }.bind(this));

        descriptor.addListener("select", function (event) {
            var nextDocument,
                currentDocument;

            if (photoshopEvent.targetOf(event) === "document") {
                if (typeof event.documentID === "number") {
                    // FIXME: This event is incorrectly triggered even when the
                    // document selection is initiated internally. Ideally it
                    // would not be, and this would be unnecessary.
                    nextDocument = this.flux.store("document").getDocument(event.documentID);

                    if (nextDocument) {
                        currentDocument = applicationStore.getCurrentDocument();

                        if (currentDocument !== nextDocument) {
                            this.flux.actions.documents.selectDocument(nextDocument);
                        }
                    } else {
                        this.flux.actions.documents.resetDocuments();
                    }
                } else {
                    this.flux.actions.documents.resetDocuments();
                }
            }
        }.bind(this));

        // Overkill, but pasting a layer just gets us a simple paste event with no descriptor
        descriptor.addListener("paste", function () {
            this.flux.actions.documents.updateCurrentDocument();
        }.bind(this));

        return this.transfer(initDocuments);
    };

    var createNew = {
        command: createNewCommand,
        reads: [locks.PS_DOC, locks.PS_APP],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var selectDocument = {
        command: selectDocumentCommand,
        reads: [locks.PS_DOC, locks.JS_DOC],
        writes: [locks.PS_DOC, locks.JS_DOC]
    };

    var selectNextDocument = {
        command: selectNextDocumentCommand,
        reads: [locks.PS_DOC, locks.JS_DOC],
        writes: [locks.PS_DOC, locks.JS_DOC]
    };

    var selectPreviousDocument = {
        command: selectPreviousDocumentCommand,
        reads: [locks.PS_DOC, locks.JS_DOC],
        writes: [locks.PS_DOC, locks.JS_DOC]
    };

    var allocateDocument = {
        command: allocateDocumentCommand,
        reads: [locks.PS_DOC, locks.PS_APP],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var disposeDocument = {
        command: disposeDocumentCommand,
        reads: [locks.PS_DOC, locks.PS_APP],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var updateDocument = {
        command: updateDocumentCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    var updateCurrentDocument = {
        command: updateCurrentDocumentCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    var initDocuments = {
        command: initDocumentsCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    var onReset = {
        command: onResetCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var onStartup = {
        command: onStartupCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    exports.createNew = createNew;
    exports.selectDocument = selectDocument;
    exports.selectNextDocument = selectNextDocument;
    exports.selectPreviousDocument = selectPreviousDocument;
    exports.allocateDocument = allocateDocument;
    exports.disposeDocument = disposeDocument;
    exports.updateDocument = updateDocument;
    exports.updateCurrentDocument = updateCurrentDocument;
    exports.initDocuments = initDocuments;
    exports.onReset = onReset;
    exports.onStartup = onStartup;
});
<commit_msg>JSDoc and explain the distance/pixels<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.
 *
 */

define(function (require, exports) {
    "use strict";

    var Promise = require("bluebird"),
        _ = require("lodash");

    var photoshopEvent = require("adapter/lib/photoshopEvent"),
        descriptor = require("adapter/ps/descriptor"),
        documentLib = require("adapter/lib/document"),
        layerLib = require("adapter/lib/layer"),
        ui = require("./ui"),
        events = require("../events"),
        locks = require("js/locks");

    /**
     * Get an array of layer descriptors for the given document descriptor.
     *
     * @private
     * @param {object} doc Document descriptor
     * @return {Promise.<Array.<object>>} Resolves with an array of layer descriptors
     */
    var _getLayersForDocument = function (doc) {
        var layerCount = doc.numberOfLayers,
            startIndex = (doc.hasBackgroundLayer ? 0 : 1),
            layerGets = _.range(layerCount, startIndex - 1, -1).map(function (i) {
                var layerReference = [
                    documentLib.referenceBy.id(doc.documentID),
                    layerLib.referenceBy.index(i)
                ];
                return descriptor.get(layerReference);
            });

        return Promise.all(layerGets);
    };

    /**
     * Creates a document in default settings
     *
     * @return {Promise}
     */
    var createNewCommand = function () {
        // 480 distance units at 300 resolution is 2000px at 72 resolution
        var docSettings = {
            width: 480,
            height: 480,
            resolution: 300,
            fill: "white",
            depth: 8,
            colorMode: "RGBColor",
            profile: "none",
            pixelAspectRation: 1
        };

        return descriptor.playObject(documentLib.create(docSettings))
            .bind(this)
            .then(function (result) {
                return this.transfer(allocateDocument, result.documentID);
            });
    };

    /**
     * Completely reset all document and layer state. This is a heavy operation
     * that should only be called in an emergency!
     *
     * @private
     * @return {Promise}
     */
    var onResetCommand = function () {
        return descriptor.getProperty("application", "numberOfDocuments")
            .bind(this)
            .then(function (docCount) {
                var payload = {};
                if (docCount === 0) {
                    payload.selectedDocumentID = null;
                    payload.documents = [];
                    this.dispatch(events.documents.RESET_DOCUMENTS, payload);
                    return;
                }

                var openDocumentPromises = _.range(1, docCount + 1)
                    .map(function (index) {
                        var indexRef = documentLib.referenceBy.index(index);
                        return descriptor.get(indexRef)
                            .then(function (doc) {
                                return _getLayersForDocument(doc)
                                    .bind(this)
                                    .then(function (layers) {
                                        return {
                                            document: doc,
                                            layers: layers
                                        };
                                    });
                            });
                    }),
                    openDocumentsPromise = Promise.all(openDocumentPromises);

                var currentRef = documentLib.referenceBy.current,
                    currentDocumentIDPromise = descriptor.getProperty(currentRef, "documentID");

                return Promise.join(currentDocumentIDPromise, openDocumentsPromise,
                    function (currentDocumentID, openDocuments) {
                        payload.selectedDocumentID = currentDocumentID;
                        payload.documents = openDocuments;
                        this.dispatch(events.documents.RESET_DOCUMENTS, payload);
                    }.bind(this));
            });
    };

    /**
     * Initialize document and layer state, emitting DOCUMENT_UPDATED and
     * CURRENT_DOCUMENT_UPDATED events for the open documents. This is different
     * from resetDocumentsCommand in two ways: 1) the emitted events are interpreted
     * by the stores as being additive (i.e., each new DOCUMENT_UPDATED event is
     * treated as indication that there is another document open); 2) these events
     * are emitted individually, and in particular the event for the current document
     * is emitted first. This is a performance optimization to allow the UI to be
     * rendered for the active document before continuing to build models for the
     * other documents.
     *
     * @return {Promise}
     */
    var initDocumentsCommand = function () {
        return descriptor.getProperty("application", "numberOfDocuments")
            .bind(this)
            .then(function (docCount) {
                if (docCount === 0) {
                    return;
                }

                var currentRef = documentLib.referenceBy.current;
                return descriptor.get(currentRef)
                    .bind(this)
                    .then(function (currentDoc) {
                        var currentDocLayersPromise = _getLayersForDocument(currentDoc)
                            .bind(this)
                            .then(function (layers) {
                                var payload = {
                                    document: currentDoc,
                                    layers: layers
                                };

                                this.dispatch(events.documents.CURRENT_DOCUMENT_UPDATED, payload);
                            });

                        var otherDocPromises = _.range(1, docCount + 1)
                            .filter(function (index) {
                                return index !== currentDoc.itemIndex;
                            })
                            .map(function (index) {
                                var indexRef = documentLib.referenceBy.index(index);
                                return descriptor.get(indexRef)
                                    .bind(this)
                                    .then(function (doc) {
                                        return _getLayersForDocument(doc)
                                            .bind(this)
                                            .then(function (layers) {
                                                var payload = {
                                                    document: doc,
                                                    layers: layers
                                                };

                                                this.dispatch(events.documents.DOCUMENT_UPDATED, payload);
                                            });
                                    });
                            }, this),
                            otherDocsPromise = Promise.all(otherDocPromises);

                        return Promise.join(currentDocLayersPromise, otherDocsPromise);
                    });
            });
    };

    /**
     * Fetch the ID of the currently selected document, or null if there is none.
     *
     * @private
     * @return {Promise.<?number>}
     */
    var _getSelectedDocumentID = function () {
        var currentRef = documentLib.referenceBy.current;
        return descriptor.getProperty(currentRef, "documentID")
            .catch(function () {
                return null;
            });
    };

    /**
     * Dispose of a previously opened document.
     *
     * @private
     * @param {!number} documentID
     * @return {Promise}
     */
    var disposeDocumentCommand = function (documentID) {
        var disposePromise = _getSelectedDocumentID()
            .bind(this)
            .then(function (currentDocumentID) {
                var payload = {
                    documentID: documentID,
                    selectedDocumentID: currentDocumentID
                };

                this.dispatch(events.documents.CLOSE_DOCUMENT, payload);
            });

        var transformPromise = this.transfer(ui.updateTransform);

        return Promise.join(disposePromise, transformPromise);
    };

    /**
     * Allocate a newly opened document. Emits DOCUMENT_UPDATED and a SELECT_DOCUMENT
     * events.
     *
     * @private
     * @param {!number} documentID
     * @return {Promise}
     */
    var allocateDocumentCommand = function (documentID) {
        var updatePromise = this.transfer(updateDocument, documentID),
            selectedDocumentPromise = _getSelectedDocumentID(),
            allocatePromise = Promise.join(selectedDocumentPromise, updatePromise,
                function (currentDocumentID) {
                    var payload = {
                        selectedDocumentID: currentDocumentID
                    };

                    this.dispatch(events.documents.SELECT_DOCUMENT, payload);
                }.bind(this));

        var transformPromise = this.transfer(ui.updateTransform);

        return Promise.join(allocatePromise, transformPromise);
    };

    /**
     * Update the document and layer state for the given document ID. Emits a
     * single DOCUMENT_UPDATED event.
     *
     * @param {number} id Document ID
     * @return {Promise}
     */
    var updateDocumentCommand = function (id) {
        var docRef = documentLib.referenceBy.id(id);
        return descriptor.get(docRef)
            .bind(this)
            .then(function (doc) {
                return _getLayersForDocument(doc)
                    .bind(this)
                    .then(function (layerArray) {
                        var payload = {
                            document: doc,
                            layers: layerArray
                        };
                        this.dispatch(events.documents.DOCUMENT_UPDATED, payload);
                    });
            });
    };

    /**
     * Update the document and layer state for the currently active document ID.
     * Emits a single CURRENT_DOCUMENT_UPDATED event.
     *
     * @return {Promise}
     */
    var updateCurrentDocumentCommand = function () {
        var currentRef = documentLib.referenceBy.current;
        return descriptor.get(currentRef)
            .bind(this)
            .then(function (doc) {
                return _getLayersForDocument(doc)
                    .bind(this)
                    .then(function (layers) {
                        var payload = {
                            document: doc,
                            layers: layers
                        };
                        this.dispatch(events.documents.CURRENT_DOCUMENT_UPDATED, payload);
                    });
            });
    };

    /**
     * Activate the given already-open document
     *
     * @param {Document} document
     * @return {Promise}
     */
    var selectDocumentCommand = function (document) {
        return descriptor.playObject(documentLib.select(documentLib.referenceBy.id(document.id)))
            .bind(this)
            .then(function () {
                var payload = {
                    selectedDocumentID: document.id
                };

                this.dispatch(events.documents.SELECT_DOCUMENT, payload);
            });
    };

    /**
     * Activate the next open document in the document index
     *
     * @return {Promise}
     */
    var selectNextDocumentCommand = function () {
        var applicationStore = this.flux.store("application"),
            nextDocument = applicationStore.getNextDocument();

        if (!nextDocument) {
            return Promise.resolve();
        }

        return this.transfer(selectDocument, nextDocument);
    };

    /**
     * Activate the previous open document in the document index
     *
     * @return {Promise}
     */
    var selectPreviousDocumentCommand = function () {
        var applicationStore = this.flux.store("application"),
            previousDocument = applicationStore.getPreviousDocument();

        if (!previousDocument) {
            return Promise.resolve();
        }

        return this.transfer(selectDocument, previousDocument);
    };

    /**
     * Register event listeners for active and open document change events, and
     * initialize the active and open document lists.
     *
     * @return {Promise}
     */
    var onStartupCommand = function () {
        var applicationStore = this.flux.store("application");

        descriptor.addListener("make", function (event) {
            var target = photoshopEvent.targetOf(event),
                currentDocument;

            switch (target) {
            case "document":
                // A new document was created
                if (typeof event.documentID === "number") {
                    this.flux.actions.documents.allocateDocument(event.documentID);
                } else {
                    this.flux.actions.documents.resetDocuments();
                }

                break;
            case "layer":
            case "contentLayer":
            case "textLayer":
                // A layer was added
                currentDocument = applicationStore.getCurrentDocument();
                this.flux.actions.documents.updateDocument(currentDocument.id);
                break;
            }
        }.bind(this));

        descriptor.addListener("open", function (event) {
            // A new document was opened
            if (typeof event.documentID === "number") {
                this.flux.actions.documents.allocateDocument(event.documentID);
            } else {
                this.flux.actions.documents.resetDocuments();
            }
        }.bind(this));

        descriptor.addListener("close", function (event) {
            // An open document was closed
            if (typeof event.documentID === "number") {
                this.flux.actions.documents.disposeDocument(event.documentID);
            } else {
                this.flux.actions.documents.resetDocuments();
            }
        }.bind(this));

        descriptor.addListener("select", function (event) {
            var nextDocument,
                currentDocument;

            if (photoshopEvent.targetOf(event) === "document") {
                if (typeof event.documentID === "number") {
                    // FIXME: This event is incorrectly triggered even when the
                    // document selection is initiated internally. Ideally it
                    // would not be, and this would be unnecessary.
                    nextDocument = this.flux.store("document").getDocument(event.documentID);

                    if (nextDocument) {
                        currentDocument = applicationStore.getCurrentDocument();

                        if (currentDocument !== nextDocument) {
                            this.flux.actions.documents.selectDocument(nextDocument);
                        }
                    } else {
                        this.flux.actions.documents.resetDocuments();
                    }
                } else {
                    this.flux.actions.documents.resetDocuments();
                }
            }
        }.bind(this));

        // Overkill, but pasting a layer just gets us a simple paste event with no descriptor
        descriptor.addListener("paste", function () {
            this.flux.actions.documents.updateCurrentDocument();
        }.bind(this));

        return this.transfer(initDocuments);
    };

    var createNew = {
        command: createNewCommand,
        reads: [locks.PS_DOC, locks.PS_APP],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var selectDocument = {
        command: selectDocumentCommand,
        reads: [locks.PS_DOC, locks.JS_DOC],
        writes: [locks.PS_DOC, locks.JS_DOC]
    };

    var selectNextDocument = {
        command: selectNextDocumentCommand,
        reads: [locks.PS_DOC, locks.JS_DOC],
        writes: [locks.PS_DOC, locks.JS_DOC]
    };

    var selectPreviousDocument = {
        command: selectPreviousDocumentCommand,
        reads: [locks.PS_DOC, locks.JS_DOC],
        writes: [locks.PS_DOC, locks.JS_DOC]
    };

    var allocateDocument = {
        command: allocateDocumentCommand,
        reads: [locks.PS_DOC, locks.PS_APP],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var disposeDocument = {
        command: disposeDocumentCommand,
        reads: [locks.PS_DOC, locks.PS_APP],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var updateDocument = {
        command: updateDocumentCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    var updateCurrentDocument = {
        command: updateCurrentDocumentCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    var initDocuments = {
        command: initDocumentsCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    var onReset = {
        command: onResetCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC, locks.JS_APP]
    };

    var onStartup = {
        command: onStartupCommand,
        reads: [locks.PS_DOC],
        writes: [locks.JS_DOC]
    };

    exports.createNew = createNew;
    exports.selectDocument = selectDocument;
    exports.selectNextDocument = selectNextDocument;
    exports.selectPreviousDocument = selectPreviousDocument;
    exports.allocateDocument = allocateDocument;
    exports.disposeDocument = disposeDocument;
    exports.updateDocument = updateDocument;
    exports.updateCurrentDocument = updateCurrentDocument;
    exports.initDocuments = initDocuments;
    exports.onReset = onReset;
    exports.onStartup = onStartup;
});
<|endoftext|>


<commit_before>import {Dialog, addDropdownBox} from "../../common"
import {tableResizeTemplate,tableInsertTemplate} from "./templates"
import {deleteTable} from "prosemirror-tables"

export class TableDialog {
    constructor(editor) {
        this.editor = editor
        this.dialogEl = false
    }

    init(){
        this.insertTableDialog()
    }

    markInsertTable(cell, className) {
        this.dialog.dialogEl.querySelectorAll(`td.${className}`).forEach(el => el.classList.remove(className))
        let colCount = 1
        let countElement = cell
        while(countElement.previousElementSibling) {
            countElement = countElement.previousElementSibling
            colCount += 1
        }
        let rowCount = 1
        countElement= countElement.parentElement
        while(countElement.previousElementSibling) {
            countElement = countElement.previousElementSibling
            rowCount += 1
        }
        // add hover class.
        const rows = this.dialog.dialogEl.querySelectorAll('tr')
        for(let i=0;i<rowCount;i++) {
            const row = rows[i]
            const cols = rows[i].querySelectorAll('td')
            for(let j=0;j<colCount;j++) {
                cols[j].classList.add(className)
            }
        }
        return {colCount, rowCount}
    }

    insertTableDialog() {
        let rowCount = 1, colCount = 1
        const buttons = []
        buttons.push({
            text: gettext('Insert'),
            classes: 'fw-dark',
            click: () => {
                const table = {type: 'table', content: []}

                for (let i=0;i<rowCount;i++) {
                    const row = {type: 'table_row', content: []}
                    for (let j=0;j<colCount;j++) {
                        row.content.push({type: 'table_cell', content: [{type: 'paragraph'}]})
                    }
                    table.content.push(row)

                }
                const schema = this.editor.currentView.state.schema
                this.editor.currentView.dispatch(
                    this.editor.currentView.state.tr.replaceSelectionWith(
                        schema.nodeFromJSON(table)
                    )
                )
                this.dialog.close()
            }
        })
        buttons.push({
            type: 'cancel'
        })

        this.dialog = new Dialog({
            title: gettext('Insert table'),
            body: tableInsertTemplate(),
            width: 360,
            height: 360,
            buttons,
            onClose: () => this.editor.currentView.focus()
        })

        this.dialog.open()

        // manage hovering over table cells
        this.dialog.dialogEl.querySelectorAll('td').forEach(el => el.addEventListener('mouseenter', () => {
            this.markInsertTable(el, 'hover')
        }))
        this.dialog.dialogEl.querySelectorAll('td').forEach(el => el.addEventListener('mouseleave', () => {
            this.dialog.dialogEl.querySelectorAll('td.hover').forEach(mEl => mEl.classList.remove('hover'))
        }))

        this.dialog.dialogEl.querySelectorAll('td').forEach(el => el.addEventListener('click', event => {
            event.preventDefault()
            event.stopImmediatePropagation()
            const newCounts = this.markInsertTable(el, 'selected')
            rowCount = newCounts.rowCount
            colCount = newCounts.colCount
        }))

    }
}

export class TableResizeDialog {
    constructor(editor) {
        this.editor = editor
        this.dialogEl = false
        this.aligned='center'
        this.width = '100'
        this.layout = 'fixed'
    }

    init() {
        let table = this.findTable(this.editor.currentView.state)
        if(table){
            this.width = table.attrs.width
            this.aligned = table.attrs.aligned
            this.layout = table.attrs.layout
        }
        this.insertDialog()
    }

    setTableAlignment() {
        this.dialog.dialogEl.querySelector('#table-alignment-btn label').innerHTML =
            document.getElementById(`table-alignment-${this.aligned}`).innerText
    }

    setTableWidth() {
        this.dialog.dialogEl.querySelector('#table-width-btn label').innerHTML =
            document.getElementById(`table-width-${this.width}`).innerText
    }

    setTableLayout() {
        this.dialog.dialogEl.querySelector('#table-layout-btn label').innerHTML =
            document.getElementById(`table-layout-${this.layout}`).innerText
    }

    findTable(state) {
        const $head = state.selection.$head
        for (let d = $head.depth; d > 0; d--) if ($head.node(d).type.spec.tableRole == "table") return $head.node(d)
        return false
    }

    submitForm(){
        let table = this.findTable(this.editor.currentView.state)
        table.attrs.width = this.width
        table.attrs['data-width'] = this.width
        this.width == "100" ? table.attrs['data-aligned'] = "center" : table.attrs['data-aligned'] = this.aligned
        this.width == "100" ? table.attrs.aligned = "center" : table.attrs.aligned = this.aligned
        table.attrs['data-layout'] = this.layout
        table.attrs.layout = this.layout
        deleteTable(this.editor.currentView.state, this.editor.currentView.dispatch)
        this.editor.currentView.dispatch(
            this.editor.currentView.state.tr.replaceSelectionWith(
                table
            )
        )
    }

    insertDialog() {
        const buttons = []
        buttons.push({
            text: gettext('Update'),
            classes: 'fw-dark',
            click: () => {
                this.submitForm()
                this.dialog.close()
            }
        })
        buttons.push({
            type: 'cancel'
        })

        this.dialog = new Dialog({
            title: gettext('Resize table'),
            body: tableResizeTemplate(),
            width: 300,
            height: 260,
            buttons,
            onClose: () => this.editor.currentView.focus()
        })

        this.dialog.open()

        this.setTableAlignment()
        this.setTableWidth()
        this.setTableLayout()

        addDropdownBox(
            document.getElementById('table-alignment-btn'),
            document.getElementById('table-alignment-pulldown')
        )

        addDropdownBox(
            document.getElementById('table-width-btn'),
            document.getElementById('table-width-pulldown')
        )

        addDropdownBox(
            document.getElementById('table-layout-btn'),
            document.getElementById('table-layout-pulldown')
        )

        document.querySelectorAll('#table-alignment-pulldown li span').forEach(el => el.addEventListener(
            'click',
            event => {
                event.preventDefault()
                this.aligned = el.id.split('-')[2]
                this.setTableAlignment()
            }
        ))

        document.querySelectorAll('#table-width-pulldown li span').forEach(el => el.addEventListener(
            'click',
            event => {
                event.preventDefault()
                this.width = el.id.split('-')[2]
                this.setTableWidth()
            }
        ))

        document.querySelectorAll('#table-layout-pulldown li span').forEach(el => el.addEventListener(
            'click',
            event => {
                event.preventDefault()
                this.layout = el.id.split('-')[2]
                this.setTableLayout()
            }
        ))
    }
}
<commit_msg>Rewrite code for consistency<commit_after>import {Dialog, addDropdownBox} from "../../common"
import {tableResizeTemplate,tableInsertTemplate} from "./templates"
import {deleteTable} from "prosemirror-tables"

export class TableDialog {
    constructor(editor) {
        this.editor = editor
        this.dialogEl = false
    }

    init(){
        this.insertTableDialog()
    }

    markInsertTable(cell, className) {
        this.dialog.dialogEl.querySelectorAll(`td.${className}`).forEach(el => el.classList.remove(className))
        let colCount = 1
        let countElement = cell
        while(countElement.previousElementSibling) {
            countElement = countElement.previousElementSibling
            colCount += 1
        }
        let rowCount = 1
        countElement= countElement.parentElement
        while(countElement.previousElementSibling) {
            countElement = countElement.previousElementSibling
            rowCount += 1
        }
        // add hover class.
        const rows = this.dialog.dialogEl.querySelectorAll('tr')
        for(let i=0;i<rowCount;i++) {
            const row = rows[i]
            const cols = rows[i].querySelectorAll('td')
            for(let j=0;j<colCount;j++) {
                cols[j].classList.add(className)
            }
        }
        return {colCount, rowCount}
    }

    insertTableDialog() {
        let rowCount = 1, colCount = 1
        const buttons = []
        buttons.push({
            text: gettext('Insert'),
            classes: 'fw-dark',
            click: () => {
                const table = {type: 'table', content: []}

                for (let i=0;i<rowCount;i++) {
                    const row = {type: 'table_row', content: []}
                    for (let j=0;j<colCount;j++) {
                        row.content.push({type: 'table_cell', content: [{type: 'paragraph'}]})
                    }
                    table.content.push(row)

                }
                const schema = this.editor.currentView.state.schema
                this.editor.currentView.dispatch(
                    this.editor.currentView.state.tr.replaceSelectionWith(
                        schema.nodeFromJSON(table)
                    )
                )
                this.dialog.close()
            }
        })
        buttons.push({
            type: 'cancel'
        })

        this.dialog = new Dialog({
            title: gettext('Insert table'),
            body: tableInsertTemplate(),
            width: 360,
            height: 360,
            buttons,
            onClose: () => this.editor.currentView.focus()
        })

        this.dialog.open()

        // manage hovering over table cells
        this.dialog.dialogEl.querySelectorAll('td').forEach(el => el.addEventListener('mouseenter', () => {
            this.markInsertTable(el, 'hover')
        }))
        this.dialog.dialogEl.querySelectorAll('td').forEach(el => el.addEventListener('mouseleave', () => {
            this.dialog.dialogEl.querySelectorAll('td.hover').forEach(mEl => mEl.classList.remove('hover'))
        }))

        this.dialog.dialogEl.querySelectorAll('td').forEach(el => el.addEventListener('click', event => {
            event.preventDefault()
            event.stopImmediatePropagation()
            const newCounts = this.markInsertTable(el, 'selected')
            rowCount = newCounts.rowCount
            colCount = newCounts.colCount
        }))

    }
}

export class TableResizeDialog {
    constructor(editor) {
        this.editor = editor
        this.dialogEl = false
        this.aligned='center'
        this.width = '100'
        this.layout = 'fixed'
    }

    init() {
        let table = this.findTable(this.editor.currentView.state)
        if(table){
            this.width = table.attrs.width
            this.aligned = table.attrs.aligned
            this.layout = table.attrs.layout
        }
        this.insertDialog()
    }

    setTableAlignment() {
        this.dialog.dialogEl.querySelector('#table-alignment-btn label').innerHTML =
            document.getElementById(`table-alignment-${this.aligned}`).innerText
    }

    setTableWidth() {
        this.dialog.dialogEl.querySelector('#table-width-btn label').innerHTML =
            document.getElementById(`table-width-${this.width}`).innerText
    }

    setTableLayout() {
        this.dialog.dialogEl.querySelector('#table-layout-btn label').innerHTML =
            document.getElementById(`table-layout-${this.layout}`).innerText
    }

    findTable(state) {
        const $head = state.selection.$head
        for (let d = $head.depth; d > 0; d--) if ($head.node(d).type.spec.tableRole == "table") return $head.node(d)
        return false
    }

    submitForm(){
        let table = this.findTable(this.editor.currentView.state)
        table.attrs.width = this.width
        table.attrs['data-width'] = this.width
        table.attrs.aligned = this.width == "100" ? "center" : this.aligned
        table.attrs['data-aligned'] = this.width == "100" ? "center" : this.aligned
        table.attrs['data-layout'] = this.layout
        table.attrs.layout = this.layout
        deleteTable(this.editor.currentView.state, this.editor.currentView.dispatch)
        this.editor.currentView.dispatch(
            this.editor.currentView.state.tr.replaceSelectionWith(
                table
            )
        )
    }

    insertDialog() {
        const buttons = []
        buttons.push({
            text: gettext('Update'),
            classes: 'fw-dark',
            click: () => {
                this.submitForm()
                this.dialog.close()
            }
        })
        buttons.push({
            type: 'cancel'
        })

        this.dialog = new Dialog({
            title: gettext('Resize table'),
            body: tableResizeTemplate(),
            width: 300,
            height: 260,
            buttons,
            onClose: () => this.editor.currentView.focus()
        })

        this.dialog.open()

        this.setTableAlignment()
        this.setTableWidth()
        this.setTableLayout()

        addDropdownBox(
            document.getElementById('table-alignment-btn'),
            document.getElementById('table-alignment-pulldown')
        )

        addDropdownBox(
            document.getElementById('table-width-btn'),
            document.getElementById('table-width-pulldown')
        )

        addDropdownBox(
            document.getElementById('table-layout-btn'),
            document.getElementById('table-layout-pulldown')
        )

        document.querySelectorAll('#table-alignment-pulldown li span').forEach(el => el.addEventListener(
            'click',
            event => {
                event.preventDefault()
                this.aligned = el.id.split('-')[2]
                this.setTableAlignment()
            }
        ))

        document.querySelectorAll('#table-width-pulldown li span').forEach(el => el.addEventListener(
            'click',
            event => {
                event.preventDefault()
                this.width = el.id.split('-')[2]
                this.setTableWidth()
            }
        ))

        document.querySelectorAll('#table-layout-pulldown li span').forEach(el => el.addEventListener(
            'click',
            event => {
                event.preventDefault()
                this.layout = el.id.split('-')[2]
                this.setTableLayout()
            }
        ))
    }
}
<|endoftext|>


<commit_before>import React from "react";
import _ from "lodash";
import { Tours, Steps } from '../collections';
import { createContainer } from 'meteor/react-meteor-data';
import { Link } from 'react-router';

import Snippet from './Snippet';
import Section from './Section';

class Step extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      highlightLineNumbers: [],
    };

    if (this.props.step) {
      const section = this.props.step.content[0];
      this.state.slug = this.props.step.slug;
      this.state.selectedIndex = 0;
    }
  }

  componentWillReceiveProps(newProps) {
    if (newProps.step && this.state.slug !== newProps.step.slug) {
      this.setState({
        slug: newProps.step.slug,
        selectedIndex: 0,
      });
    }
  }

  onSelect(index) {
    this.setState({
      selectedIndex: index,
    });
  }

  getLineNumbersForCurrentSection() {
    return this.props.step && this.getLineNumbersForSection(this.props.step.content[this.state.selectedIndex]);
  }

  getLineNumbersForSection(section) {
    return _.range(parseInt(section.lineStart, 10), parseInt(section.lineEnd, 10) + 1);
  }

  getStepNumber(slug) {
    return _.indexOf(this.props.tour.steps, slug) + 1;
  }

  getStepLink(step) {
    return `/tour/${this.props.tour.repository}/${step}`;
  }

  getTourLink() {
    return `/tour/${this.props.tour.repository}`;
  }

  getNextStepLink() {
    const curIndex = _.indexOf(this.props.tour.steps, this.props.step.slug);
    const nextStep = this.props.tour.steps[curIndex + 1];
    if (nextStep) {
      return (
        <Link className="next-step btn btn-default" to={this.getStepLink(nextStep)}>
          {this.props.step.getNextStep().getFullTitle()}

        </Link>
        );
    }
  }

  getPrevStepLink() {
    const curIndex = _.indexOf(this.props.tour.steps, this.props.step.slug);
    const prevStep = this.props.tour.steps[curIndex - 1];
    if (prevStep) {
      return (
        <Link to={this.getStepLink(prevStep)} className="btn btn-default">

          {this.props.step.getPrevStep().getFullTitle()}
        </Link>
        );
    }
  }

  render() {
    if (!this.props.step) {
      return
Loading...

    }

    return (

{this.props.step.fullRepoName}/{this.props.step.filePath}

          <Snippet
            code={this.props.step.code}
            highlightLineNumbers={this.getLineNumbersForCurrentSection()}/>

          <Link to={'/'} className="tiny-logo">CodeTours</Link>  |
          <Link to={this.getTourLink()}>Tour of {this.props.tour.targetRepository}</Link>

{this.props.step.getFullTitle()}

          {
            _.map(this.props.step.content, (section, index) => {
              return (

              );
            })
          }

            {this.getPrevStepLink()}
            {this.getNextStepLink()}

    );

  }
}

const StepContainer = createContainer(({ params }) => {
  const tourName = `${params.user}/${params.repoName}`;
  Meteor.subscribe('steps', tourName);
  return {
    step: Steps.findOne(
      {
        tourName,
        slug: params.stepSlug
      }
    ),
    tour: Tours.findOne({
      repository: tourName
    })
  }
}, Step);

export default StepContainer;
<commit_msg>Put back buttons<commit_after>import React from "react";
import _ from "lodash";
import { Tours, Steps } from '../collections';
import { createContainer } from 'meteor/react-meteor-data';
import { Link } from 'react-router';

import Snippet from './Snippet';
import Section from './Section';

class Step extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      highlightLineNumbers: [],
    };

    if (this.props.step) {
      const section = this.props.step.content[0];
      this.state.slug = this.props.step.slug;
      this.state.selectedIndex = 0;
    }
  }

  componentWillReceiveProps(newProps) {
    if (newProps.step && this.state.slug !== newProps.step.slug) {
      this.setState({
        slug: newProps.step.slug,
        selectedIndex: 0,
      });
    }
  }

  onSelect(index) {
    this.setState({
      selectedIndex: index,
    });
  }

  getLineNumbersForCurrentSection() {
    return this.props.step && this.getLineNumbersForSection(this.props.step.content[this.state.selectedIndex]);
  }

  getLineNumbersForSection(section) {
    return _.range(parseInt(section.lineStart, 10), parseInt(section.lineEnd, 10) + 1);
  }

  getStepNumber(slug) {
    return _.indexOf(this.props.tour.steps, slug) + 1;
  }

  getStepLink(step) {
    return `/tour/${this.props.tour.repository}/${step}`;
  }

  getTourLink() {
    return `/tour/${this.props.tour.repository}`;
  }

  getNextStepLink() {
    const curIndex = _.indexOf(this.props.tour.steps, this.props.step.slug);
    const nextStep = this.props.tour.steps[curIndex + 1];
    if (nextStep) {
      return (
        <Link className="next-step btn btn-default" to={this.getStepLink(nextStep)}>
          {this.props.step.getNextStep().getFullTitle()}

        </Link>
        );
    }
  }

  getPrevStepLink() {
    const curIndex = _.indexOf(this.props.tour.steps, this.props.step.slug);
    const prevStep = this.props.tour.steps[curIndex - 1];
    if (prevStep) {
      return (
        <Link to={this.getStepLink(prevStep)} className="btn btn-default">

          {this.props.step.getPrevStep().getFullTitle()}
        </Link>
        );
    }
  }

  render() {
    if (!this.props.step) {
      return
Loading...

    }

    return (

{this.props.step.fullRepoName}/{this.props.step.filePath}

          <Snippet
            code={this.props.step.code}
            highlightLineNumbers={this.getLineNumbersForCurrentSection()}/>

          <Link to={'/'} className="tiny-logo">CodeTours</Link>  |
          <Link to={this.getTourLink()}>Tour of {this.props.tour.targetRepository}</Link>

{this.props.step.getFullTitle()}

            {this.getPrevStepLink()}
            {this.getNextStepLink()}

          {
            _.map(this.props.step.content, (section, index) => {
              return (

              );
            })
          }

            {this.getPrevStepLink()}
            {this.getNextStepLink()}

    );

  }
}

const StepContainer = createContainer(({ params }) => {
  const tourName = `${params.user}/${params.repoName}`;
  Meteor.subscribe('steps', tourName);
  return {
    step: Steps.findOne(
      {
        tourName,
        slug: params.stepSlug
      }
    ),
    tour: Tours.findOne({
      repository: tourName
    })
  }
}, Step);

export default StepContainer;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 * Copyright 2017 The FOAM Authors. 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.
 */

foam.CLASS({
  package: 'foam.comics',
  name: 'DAOController',

  requires: [
    'foam.comics.SearchMode',
    'foam.u2.borders.NullBorder',
    'foam.nanos.export.CSVDriver'
  ],

  topics: [
    'finished'
  ],

  properties: [
    {
      name: 'data',
      hidden: true
    },
    {
      name: 'predicate'
    },
    {
      name: 'csvDriver',
      factory: function() {
        return this.CSVDriver.create();
      }
    },
    {
      name: 'filteredDAO',
      view: { class: 'foam.u2.view.ScrollTableView' },
      expression: function(data, predicate) {
        return predicate ? data.where(predicate) : data;
      }
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection',
      hidden: true
    },
    {
      class: 'Boolean',
      name: 'createEnabled',
      documentation: 'True to enable the create button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'editEnabled',
      documentation: 'True to enable the edit button',
      value: true
    },
    {
      class: 'Boolean',
      name: 'selectEnabled',
      documentation: 'True to enable the select button.',
      value: false
    },
    {
      class: 'Boolean',
      name: 'addEnabled',
      documentation: 'True to enable the Add button for adding to a relationship',
      value: false
    },
    {
      class: 'Boolean',
      name: 'exportEnabled',
      documentation: 'True to enable the export button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'exportCSVEnabled',
      documentation: 'True to enable export csv button'
    },
    {
      class: 'Boolean',
      name: 'toggleEnabled',
      documentation: 'True to enable the toggle filters button.',
      value: true
    },
    {
      name: 'border',
      documentation: `
        If you want the DAO controller to be the content of a border view, set
        the border here.
      `,
      factory: function() { return this.NullBorder.create(); }
    },
    {
      class: 'Boolean',
      name: 'searchHidden',
      documentation: `Used internally to keep track of whether the search panel
        is currently hidden or not.`,
      value: false
    },
    {
      class: 'Enum',
      of: 'foam.comics.SearchMode',
      name: 'searchMode',
      documentation: `
        The level of search capabilities that the controller should have.
      `,
      factory: function() {
        return this.SearchMode.FULL;
      }
    },
    {
      name: 'searchColumns',
      documentation: `
        Lets you pick which properties on the model should be used as search
        filters. You should set the search columns on the model itself and only
        set this property when you want to override the ones set on the model.
      `
    },
    {
      class: 'String',
      name: 'title',
      expression: function(data$of) {
        return 'Browse ' + data$of.model_.plural;
      }
    },
    {
      class: 'String',
      name: 'subtitle'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.core.Action',
      name: 'primaryAction',
      documentation: `
        The most important action on the page. The view for this controller may
        choose to display this action prominently.
      `,
      factory: function() {
        return this.relationship
          ? this.addEnabled
            ? this.ADD_SELECTION
            : this.SELECT
          : this.cls_.CREATE;
      }
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'summaryView',
      documentation: `
        Subclasses can set this to override the default summaryView.
      `
    },
    {
      class: 'String',
      name: 'createLabel',
      documentation: `
        Set this to override the label of the create button, which is the
        default primary action.
      `
    },
    {
      class: 'String',
      name: 'detailView',
      value: 'foam.u2.DetailView'
    }
  ],

  actions: [
    {
      name: 'toggleFilters',
      isAvailable: function(toggleEnabled) { return toggleEnabled; },
      code: function() {
        this.searchHidden = ! this.searchHidden;
      }
    },
    {
      name: 'create',
      isAvailable: function(createEnabled) { return createEnabled; },
      code: function() { }
    },
    {
      name: 'edit',
      isEnabled: function(selection) { return !! selection; },
      isAvailable: function(editEnabled) { return editEnabled; },
      code: function() {
        this.pub('edit', this.selection.id);
      }
    },
    {
      name: 'findRelatedObject',
      label: 'Add',
      isAvailable: function(relationship, addEnabled) {
        // Only enable the Add button if we're not already trying to choose a selected item for a relationship.
        return !! ( relationship && relationship.junctionDAO ) && ! addEnabled;
      },
      code: function() { }
    },
    {
      name: 'addSelection',
      label: 'Add',
      isAvailable: function(addEnabled) { return addEnabled; },
      code: function() {
        var self = this;
        this.relationship.add(this.selection).then(function() {
          self.finished.pub();
        });
      }
    },
    {
      name: 'select',
      isAvailable: function(selectEnabled) { return selectEnabled; },
      isEnabled: function(selection) { return !! selection; },
      code: function() {
        this.pub('select', this.selection.id);
        this.finished.pub();
      }
    },
    {
      name: 'export',
      isAvailable: function(exportEnabled) { return exportEnabled; },
      code: function() {
        this.pub('export', this.filteredDAO);
      }
    },
    {
      name: 'exportCSV',
      label: 'Export as CSV',
      icon: 'images/export-icon-resting.svg',
      isAvailable: function(exportCSVEnabled) { return exportCSVEnabled; },
      code: function() {
        this.downloadCSV(this.filteredDAO);
      }
    }
  ],

  listeners: [
    function downloadCSV(data) {
      this.csvDriver.exportDAO(this.__context__, data)
      .then(function(result) {
        result = 'data:text/csv;charset=utf-8,' + result;
        var encodedUri = encodeURI(result);
        var link = document.createElement('a');
        link.setAttribute('href', encodedUri);
        link.setAttribute('download', 'data.csv');
        document.body.appendChild(link);
        link.click();
      });
    }
  ]
});
<commit_msg>Disable add button until selection made<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 * Copyright 2017 The FOAM Authors. 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.
 */

foam.CLASS({
  package: 'foam.comics',
  name: 'DAOController',

  requires: [
    'foam.comics.SearchMode',
    'foam.u2.borders.NullBorder',
    'foam.nanos.export.CSVDriver'
  ],

  topics: [
    'finished'
  ],

  properties: [
    {
      name: 'data',
      hidden: true
    },
    {
      name: 'predicate'
    },
    {
      name: 'csvDriver',
      factory: function() {
        return this.CSVDriver.create();
      }
    },
    {
      name: 'filteredDAO',
      view: { class: 'foam.u2.view.ScrollTableView' },
      expression: function(data, predicate) {
        return predicate ? data.where(predicate) : data;
      }
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection',
      hidden: true
    },
    {
      class: 'Boolean',
      name: 'createEnabled',
      documentation: 'True to enable the create button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'editEnabled',
      documentation: 'True to enable the edit button',
      value: true
    },
    {
      class: 'Boolean',
      name: 'selectEnabled',
      documentation: 'True to enable the select button.',
      value: false
    },
    {
      class: 'Boolean',
      name: 'addEnabled',
      documentation: 'True to enable the Add button for adding to a relationship',
      value: false
    },
    {
      class: 'Boolean',
      name: 'exportEnabled',
      documentation: 'True to enable the export button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'exportCSVEnabled',
      documentation: 'True to enable export csv button'
    },
    {
      class: 'Boolean',
      name: 'toggleEnabled',
      documentation: 'True to enable the toggle filters button.',
      value: true
    },
    {
      name: 'border',
      documentation: `
        If you want the DAO controller to be the content of a border view, set
        the border here.
      `,
      factory: function() { return this.NullBorder.create(); }
    },
    {
      class: 'Boolean',
      name: 'searchHidden',
      documentation: `Used internally to keep track of whether the search panel
        is currently hidden or not.`,
      value: false
    },
    {
      class: 'Enum',
      of: 'foam.comics.SearchMode',
      name: 'searchMode',
      documentation: `
        The level of search capabilities that the controller should have.
      `,
      factory: function() {
        return this.SearchMode.FULL;
      }
    },
    {
      name: 'searchColumns',
      documentation: `
        Lets you pick which properties on the model should be used as search
        filters. You should set the search columns on the model itself and only
        set this property when you want to override the ones set on the model.
      `
    },
    {
      class: 'String',
      name: 'title',
      expression: function(data$of) {
        return 'Browse ' + data$of.model_.plural;
      }
    },
    {
      class: 'String',
      name: 'subtitle'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.core.Action',
      name: 'primaryAction',
      documentation: `
        The most important action on the page. The view for this controller may
        choose to display this action prominently.
      `,
      factory: function() {
        return this.relationship
          ? this.addEnabled
            ? this.ADD_SELECTION
            : this.SELECT
          : this.cls_.CREATE;
      }
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'summaryView',
      documentation: `
        Subclasses can set this to override the default summaryView.
      `
    },
    {
      class: 'String',
      name: 'createLabel',
      documentation: `
        Set this to override the label of the create button, which is the
        default primary action.
      `
    },
    {
      class: 'String',
      name: 'detailView',
      value: 'foam.u2.DetailView'
    }
  ],

  actions: [
    {
      name: 'toggleFilters',
      isAvailable: function(toggleEnabled) { return toggleEnabled; },
      code: function() {
        this.searchHidden = ! this.searchHidden;
      }
    },
    {
      name: 'create',
      isAvailable: function(createEnabled) { return createEnabled; },
      code: function() { }
    },
    {
      name: 'edit',
      isEnabled: function(selection) { return !! selection; },
      isAvailable: function(editEnabled) { return editEnabled; },
      code: function() {
        this.pub('edit', this.selection.id);
      }
    },
    {
      name: 'findRelatedObject',
      label: 'Add',
      isAvailable: function(relationship, addEnabled) {
        // Only enable the Add button if we're not already trying to choose a selected item for a relationship.
        return !! ( relationship && relationship.junctionDAO ) && ! addEnabled;
      },
      code: function() { }
    },
    {
      name: 'addSelection',
      label: 'Add',
      isAvailable: function(addEnabled) { return addEnabled; },
      isEnabled: function(selection) { return !! selection },
      code: function() {
        var self = this;
        this.relationship.add(this.selection).then(function() {
          self.finished.pub();
        });
      }
    },
    {
      name: 'select',
      isAvailable: function(selectEnabled) { return selectEnabled; },
      isEnabled: function(selection) { return !! selection; },
      code: function() {
        this.pub('select', this.selection.id);
        this.finished.pub();
      }
    },
    {
      name: 'export',
      isAvailable: function(exportEnabled) { return exportEnabled; },
      code: function() {
        this.pub('export', this.filteredDAO);
      }
    },
    {
      name: 'exportCSV',
      label: 'Export as CSV',
      icon: 'images/export-icon-resting.svg',
      isAvailable: function(exportCSVEnabled) { return exportCSVEnabled; },
      code: function() {
        this.downloadCSV(this.filteredDAO);
      }
    }
  ],

  listeners: [
    function downloadCSV(data) {
      this.csvDriver.exportDAO(this.__context__, data)
      .then(function(result) {
        result = 'data:text/csv;charset=utf-8,' + result;
        var encodedUri = encodeURI(result);
        var link = document.createElement('a');
        link.setAttribute('href', encodedUri);
        link.setAttribute('download', 'data.csv');
        document.body.appendChild(link);
        link.click();
      });
    }
  ]
});
<|endoftext|>


<commit_before>test_basic_files = [
  'vendor/lodash.min.js',
  'build/react-three.js',
  'test/createTestFixtureMountPoint.js', // why did I make this filename so long/
  'test/basics/*.js'
];

module.exports = function(config) {
  config.set({
    browsers: ['Firefox'],
    frameworks:['jasmine'],
    files: test_basic_files,
    singleRun:true
  });
};
<commit_msg>Add 'var' to declaration in karam config<commit_after>var test_basic_files = [
  'vendor/lodash.min.js',
  'build/react-three.js',
  'test/createTestFixtureMountPoint.js', // why did I make this filename so long/
  'test/basics/*.js'
];

module.exports = function(config) {
  config.set({
    browsers: ['Firefox'],
    frameworks:['jasmine'],
    files: test_basic_files,
    singleRun:true
  });
};
<|endoftext|>


<commit_before>'use strict';

var module = angular.module('module',['ngResource', 'ngNestedResource']);

describe("Nested Resource", function(){

    beforeEach(module('module'));

    var organizations, httpBackend;

    beforeEach(inject(function($httpBackend, nestedResource){

        httpBackend = $httpBackend;

        organizations = nestedResource("/api/organizations/", {
            getAll: {method: "GET", isArray: true},
            create: {method: "POST"},
            get: {
                route: "@/",
                method: "GET",
                nested: {
                    update: { method: "PUT" },
                    delete: { method: "DELETE" },
                    workers: {
                        method: "GET",
                        route: "workers/",
                        isArray: true,
                        nested: {
                            update: {
                                method: "PUT",
                                route: "@/"
                            }
                        }
                    }
                }
            }
        });
    }));

    it("should verify all calls are handled correctly", function(){

        organizations.getAll();
        httpBackend.expectGET("/api/organizations").respond([{id:1, name:"Google.com"}]);
        httpBackend.flush();

        organizations.create({name: "Google.com"});
        httpBackend.expectPOST("/api/organizations").respond({id:1, name:"Google.com"});
        httpBackend.flush();

        var organization = organizations.get(1);
        httpBackend.expectGET("/api/organizations/1").respond({id:1, name:"Google.com"});
        httpBackend.flush();

        organization.$update({name: "Bing.com"});
        httpBackend.expectPUT("/api/organizations/1").respond({id:1, name:"Bing.com"});
        httpBackend.flush();

        var workers = organization.$workers();
        httpBackend.expectGET("/api/organizations/1/workers?").respond([{id:264, name:"No Name"}]);
        httpBackend.flush();

        workers.$update(264, {name: "Steve Ballmer"});
        httpBackend.expectPUT("/api/organizations/1/workers", {param:264, name: "Steve Ballmer"})
            .respond({id:264, name:"Steve Ballmer"});
        httpBackend.flush();

    })

})<commit_msg>Update NestedResrourceSpec.js<commit_after>'use strict';

var module = angular.module('module',['ngResource', 'ngNestedResource']);

describe("Nested Resource", function(){

    beforeEach(module('module'));

    var organizations, httpBackend;

    beforeEach(inject(function($httpBackend, nestedResource){

        httpBackend = $httpBackend;

        organizations = nestedResource("/api/organizations/", {
            getAll: {method: "GET", isArray: true},
            create: {method: "POST"},
            get: {
                route: "@/",
                method: "GET",
                nested: {
                    update: { method: "PUT" },
                    delete: { method: "DELETE" },
                    workers: {
                        method: "GET",
                        route: "workers/",
                        isArray: true,
                        nested: {
                            update: {
                                method: "PUT",
                                route: "@/"
                            }
                        }
                    }
                }
            }
        });
    }));

    it("should verify all calls are handled correctly", function(){

        organizations.getAll();
        httpBackend.expectGET("/api/organizations").respond([{id:1, name:"Google.com"}]);
        httpBackend.flush();

        organizations.create({name: "Google.com"});
        httpBackend.expectPOST("/api/organizations").respond({id:1, name:"Google.com"});
        httpBackend.flush();

        var organization = organizations.get(1);
        httpBackend.expectGET("/api/organizations/1").respond({id:1, name:"Google.com"});
        httpBackend.flush();

        organization.$update({name: "Bing.com"});
        httpBackend.expectPUT("/api/organizations/1", {name: "Bing.com"}).respond({id:1, name:"Bing.com"});
        httpBackend.flush();

        var workers = organization.$workers();
        httpBackend.expectGET("/api/organizations/1/workers?").respond([{id:264, name:"No Name"}]);
        httpBackend.flush();

        workers.$update(264, {name: "Steve Ballmer"});
        httpBackend.expectPUT("/api/organizations/1/workers", {param:264, name: "Steve Ballmer"})
            .respond({id:264, name:"Steve Ballmer"});
        httpBackend.flush();

    })

})
<|endoftext|>