<commit_before>/*
 * @requires Folds.js
 */

Folds.FoldSummaryForm = Ext.extend(gxp.plugins.Tool, {

    ptype: "app_foldsummaryform",

    /** api: config[featureManager]
     *  ``String`` id of the FeatureManager to add uploaded features to
     */

    /** api: config[featureEditor]
     *  ``String`` id of the FeatureEditor to modify uploaded features
     */

    /** api: config[temporaryWorkspace]
     *  ``String`` temporary GeoServer workspace for shapefile uploads.
     *  Default is "temp".
     */
    temporaryWorkspace: "temp",

    /** api: config[temporaryWorkspaceNamespaceUri]
     *  ``String`` namespace uri of the temporary GeoServer workspace for
     *  shapefile uploads. Default is "http://geonode.org/temporary".
     */
    temporaryWorkspaceNamespaceUri: "http://geonode.org/temporary",

    /** private: property[sessionFids]
     *  ``Array`` fids of features added/modified in this session
     */
    sessionFids: [],

    autoActivate: false,

    init: function(target) {
        Folds.FoldSummaryForm.superclass.init.apply(this, arguments);

        this.sessionFids = [];
        this.fold= {};
        var featureManager = target.tools[this.featureManager];
        featureManager.featureLayer.events.on({
            "featureselected": function(e) {
                if (!e.feature.fid) {
                    return;
                }
                if (featureManager.layerRecord.get("name") == "geonode:fold_section_view") {
                    this.current_fold_section_url = "/observations/foldsection/join";
                    this.sessionFids.push(e.feature.fid.split('.')[1]);
                }
            },
            "featureunselected": function(e) {
                if (this.active && featureManager.layerRecord.get("name") == "geonode:fold_section_view") {
                    this.sessionFids = [];
                    this.target.summaryId = null;
                }
            },
            scope: this
        });
    },

    addOutput: function(config) {
        return Folds.FoldSummaryForm.superclass.addOutput.call(this, {
            xtype: "form",
            labelWidth: 110,
            defaults: {
                anchor: "100%"
            },
            items: [{
                xtype: "container",
                layout: "hbox",
                cls: "composite-wrap",
                fieldLabel: "Modify a Neotectonic fold section",
                items: [{
                    id: this.id + "_tooltarget",
                    xtype: "container",
                    cls: "toolbar-spaced",
                    layout: "toolbar"
                }]
            }, {
                xtype: "box",
                autoEl: {
                    tag: "p",
                    cls: "x-form-item"
                },
                html: "To create a Neotectonic fold, select a record by holding down ctl or shift to select multiple records. Then click join."
            }, {
                xtype: "textfield",
                ref: "fold",
                fieldLabel: "Neotectonic Fault Name",
                //validationDelay: 500,
                listeners: {
                        "valid": this.updateFaultSectionName,
                        scope: this
                }
             }, {
                xtype: "container",
                layout: "hbox",
                fieldLabel: "Join sections",
                items: [{
                    xtype: "button",
                    text: "Join",
                    iconCls: "icon-layer-switcher",
                    handler: function() {
                        var featureManager = this.target.tools[this.featureManager];
                        this.sessionFids.push(this.fold);
                        Ext.Ajax.request({
                            method: "PUT",
                            url: this.target.localGeoNodeUrl + this.target.localHostname + this.current_fold_section_url,
                            params: Ext.encode(this.sessionFids),
                            success: function(response, opts) {
                                alert('Fault created');
                                this.sessionFids = [];
                            },
                            failure: function(response, opts){
                                alert('Failed to create the Fault');
                            },

                            scope: this
                        });

                    },
                    scope: this
                    }]
            }],
            listeners: {
                "added": function(cmp, ct) {
                    ct.on({
                        "expand": function() { this.activate(); },
                        "collapse": function() { this.deactivate(); },
                        scope: this
                    });
                },
                scope: this
            }
        });
    },
    updateFaultSectionName: function() {
        var form = this.output[0]
        if (form.fold.getValue()) {
                this.fold['name'] = form.fold.getValue()
        }
    },
    activate: function() {
        if (Folds.FoldSummaryForm.superclass.activate.apply(this, arguments)) {
            var featureManager = this.target.tools[this.featureManager];
            featureManager.setLayer();
            if (!this.layerRecord) {
                this.target.createLayerRecord({
                    name: "geonode:fold_section_view",
                    source: "local"
                }, function(record) {
                    this.layerRecord = record;
                    featureManager.setLayer(record);
                }, this);
            } else {
                featureManager.setLayer(this.layerRecord);
            }
            featureManager.on("layerchange", function(mgr, rec) {
                mgr.featureStore.on({
                    "save": function(store, batch, data) {
                        var fid;
                        for (var action in data) {
                            for (var i=data[action].length-1; i>=0; --i) {
                                fid = data[action][i].feature.fid;
                                this.sessionFids.remove(fid);
                                if (action != "destroy") {
                                    this.sessionFids.push(fid);
                                }
                            }
                        }
                    },
                    "load": function() {
                        this.target.summaryId && window.setTimeout((function() {
                            var feature = mgr.featureLayer.getFeatureByFid(this.target.summaryId);
                            if (feature && feature.layer.selectedFeatures.indexOf(feature) == -1) {
                                feature.layer.selectedFeatures.push(feature);
                                feature.layer.events.triggerEvent("featureselected", {feature: feature});
                            }
                        }).createDelegate(this), 0);
                    },
                    scope: this
                });
            }, this, {single: true});
        }
    },

    deactivate: function() {
        if (Folds.FoldSummaryForm.superclass.deactivate.apply(this, arguments)) {
            this.target.tools[this.featureManager].featureStore.un("save", this.monitorSave, this);
        }
    },

    showUploadWindow: function() {
        var uploadWindow = new Ext.Window({
            title: "Import Neotectonic folds",
            width: 250,
            autoHeight: true,
            modal: true,
            items: [{
                xtype: "form",
                ref: "form",
                padding: 10,
                border: false,
                autoHeight: true,
                labelWidth: 40,
                defaults: {
                    anchor: "100%"
                },
                items: [{
                    xtype: "box",
                    autoEl: {
                        tag: "p",
                        cls: "x-form-item"
                    },
                    html: "Select a zipped shapefile for uploading. The shapefile needs to have a line geometry."
                }, {
                    xtype: "fileuploadfield",
                    ref: "fileField",
                    fieldLabel: "File",
                    allowBlank: false,
                    listeners: {
                        "fileselected": function(field, file) {
                            field.ownerCt.uploadButton.enable();
                        }
                    }
                }],
                buttonAlign: "center",
                buttons: [{
                    text: "Upload",
                    ref: "../uploadButton",
                    disabled: true,
                    handler: function() {
                        var file = uploadWindow.form.fileField.fileInput.dom.files[0];
                        Ext.Ajax.request({
                            method: "PUT",
                            url: this.target.localGeoServerUrl + "rest/workspaces/" +
                                this.temporaryWorkspace + "/datastores/" +
                                file.fileName + "/file.shp?update=overwrite",
                            xmlData: file,
                            headers: {
                                "Content-Type": file.fileName.split(".").pop().toLowerCase() == "zip" ?
                                    "application/zip" : file.type
                            },
                            success: this.handleUpload.createDelegate(this,
                                [file.fileName, uploadWindow], true),
                            scope: this
                        });
                    },
                    scope: this
                }]
            }]
        });
        uploadWindow.show();
    },

    handleUpload: function(response, options, fileName, uploadWindow) {
        uploadWindow.close();
        var fileParts = fileName.split(".");
        fileParts.pop();
        var layerName = fileParts.join("");
        new OpenLayers.Protocol.WFS({
            version: "1.1.0",
            srsName: this.target.mapPanel.map.getProjectionObject().getCode(),
            url: this.target.localGeoServerUrl + "wfs",
            featureType: layerName,
            featureNS: this.temporaryWorkspaceNamespaceUri,
            outputFormat: "GML2"
        }).read({
            callback: function(response) {
                var extent = new OpenLayers.Bounds();
                var features = response.features, feature, date;
                for (var i=features.length-1; i>=0; --i) {
                    feature = features[i];
                    extent.extend(feature.geometry.getBounds());
                    feature.fid = null;
                    feature.state = OpenLayers.State.INSERT;
                    // convert dates
                    for (var a in feature.attributes) {
                        date = Date.parseDate(feature.attributes[a], "Y/m/d");
                        if (date) {
                            feature.attributes[a] = date.format("c");
                        }
                    }
                }
                var featureManager = this.target.tools[this.featureManager];
                featureManager.featureLayer.addFeatures(features);
                featureManager.featureStore.save();

                var featureEditor = this.target.tools[this.featureEditor];
                featureEditor.actions[1].control.activate();
                this.target.mapPanel.map.zoomToExtent(extent);
            },
            scope: this
        });
        //TODO remove uploaded layer/store/style or call GeoNode updatelayers
    }

});

Ext.preg(Folds.FoldSummaryForm.prototype.ptype, Folds.FoldSummaryForm);
<commit_msg>small change to fold summary<commit_after>/*
 * @requires Folds.js
 */

Folds.FoldSummaryForm = Ext.extend(gxp.plugins.Tool, {

    ptype: "app_foldsummaryform",

    /** api: config[featureManager]
     *  ``String`` id of the FeatureManager to add uploaded features to
     */

    /** api: config[featureEditor]
     *  ``String`` id of the FeatureEditor to modify uploaded features
     */

    /** api: config[temporaryWorkspace]
     *  ``String`` temporary GeoServer workspace for shapefile uploads.
     *  Default is "temp".
     */
    temporaryWorkspace: "temp",

    /** api: config[temporaryWorkspaceNamespaceUri]
     *  ``String`` namespace uri of the temporary GeoServer workspace for
     *  shapefile uploads. Default is "http://geonode.org/temporary".
     */
    temporaryWorkspaceNamespaceUri: "http://geonode.org/temporary",

    /** private: property[sessionFids]
     *  ``Array`` fids of features added/modified in this session
     */
    sessionFids: [],

    autoActivate: false,

    init: function(target) {
        Folds.FoldSummaryForm.superclass.init.apply(this, arguments);

        this.sessionFids = [];
        this.fold= {};
        var featureManager = target.tools[this.featureManager];
        featureManager.featureLayer.events.on({
            "featureselected": function(e) {
                if (!e.feature.fid) {
                    return;
                }
                if (featureManager.layerRecord.get("name") == "geonode:fold_section_view") {
                    this.current_fold_section_url = "/observations/foldsection/join";
                    this.sessionFids.push(e.feature.fid.split('.')[1]);
                }
            },
            "featureunselected": function(e) {
                if (this.active && featureManager.layerRecord.get("name") == "geonode:fold_section_view") {
                    this.sessionFids = [];
                    this.target.foldsummaryId = null;
                }
            },
            scope: this
        });
    },

    addOutput: function(config) {
        return Folds.FoldSummaryForm.superclass.addOutput.call(this, {
            xtype: "form",
            labelWidth: 110,
            defaults: {
                anchor: "100%"
            },
            items: [{
                xtype: "container",
                layout: "hbox",
                cls: "composite-wrap",
                fieldLabel: "Modify a Neotectonic fold section",
                items: [{
                    id: this.id + "_tooltarget",
                    xtype: "container",
                    cls: "toolbar-spaced",
                    layout: "toolbar"
                }]
            }, {
                xtype: "box",
                autoEl: {
                    tag: "p",
                    cls: "x-form-item"
                },
                html: "To create a Neotectonic fold, select a record by holding down ctl or shift to select multiple records. Then click join."
            }, {
                xtype: "textfield",
                ref: "fold",
                fieldLabel: "Neotectonic Fault Name",
                //validationDelay: 500,
                listeners: {
                        "valid": this.updateFaultSectionName,
                        scope: this
                }
             }, {
                xtype: "container",
                layout: "hbox",
                fieldLabel: "Join sections",
                items: [{
                    xtype: "button",
                    text: "Join",
                    iconCls: "icon-layer-switcher",
                    handler: function() {
                        var featureManager = this.target.tools[this.featureManager];
                        this.sessionFids.push(this.fold);
                        Ext.Ajax.request({
                            method: "PUT",
                            url: this.target.localGeoNodeUrl + this.target.localHostname + this.current_fold_section_url,
                            params: Ext.encode(this.sessionFids),
                            success: function(response, opts) {
                                alert('Fault created');
                                this.sessionFids = [];
                            },
                            failure: function(response, opts){
                                alert('Failed to create the Fault');
                            },

                            scope: this
                        });

                    },
                    scope: this
                    }]
            }],
            listeners: {
                "added": function(cmp, ct) {
                    ct.on({
                        "expand": function() { this.activate(); },
                        "collapse": function() { this.deactivate(); },
                        scope: this
                    });
                },
                scope: this
            }
        });
    },
    updateFaultSectionName: function() {
        var form = this.output[0]
        if (form.fold.getValue()) {
                this.fold['name'] = form.fold.getValue()
        }
    },
    activate: function() {
        if (Folds.FoldSummaryForm.superclass.activate.apply(this, arguments)) {
            var featureManager = this.target.tools[this.featureManager];
            featureManager.setLayer();
            if (!this.layerRecord) {
                this.target.createLayerRecord({
                    name: "geonode:fold_section_view",
                    source: "local"
                }, function(record) {
                    this.layerRecord = record;
                    featureManager.setLayer(record);
                }, this);
            } else {
                featureManager.setLayer(this.layerRecord);
            }
            featureManager.on("layerchange", function(mgr, rec) {
                mgr.featureStore.on({
                    "save": function(store, batch, data) {
                        var fid;
                        for (var action in data) {
                            for (var i=data[action].length-1; i>=0; --i) {
                                fid = data[action][i].feature.fid;
                                this.sessionFids.remove(fid);
                                if (action != "destroy") {
                                    this.sessionFids.push(fid);
                                }
                            }
                        }
                    },
                    "load": function() {
                        this.target.foldsummaryId && window.setTimeout((function() {
                            var feature = mgr.featureLayer.getFeatureByFid(this.target.foldsummaryId);
                            if (feature && feature.layer.selectedFeatures.indexOf(feature) == -1) {
                                feature.layer.selectedFeatures.push(feature);
                                feature.layer.events.triggerEvent("featureselected", {feature: feature});
                            }
                        }).createDelegate(this), 0);
                    },
                    scope: this
                });
            }, this, {single: true});
        }
    },

    deactivate: function() {
        if (Folds.FoldSummaryForm.superclass.deactivate.apply(this, arguments)) {
            this.target.tools[this.featureManager].featureStore.un("save", this.monitorSave, this);
        }
    },

    showUploadWindow: function() {
        var uploadWindow = new Ext.Window({
            title: "Import Neotectonic folds",
            width: 250,
            autoHeight: true,
            modal: true,
            items: [{
                xtype: "form",
                ref: "form",
                padding: 10,
                border: false,
                autoHeight: true,
                labelWidth: 40,
                defaults: {
                    anchor: "100%"
                },
                items: [{
                    xtype: "box",
                    autoEl: {
                        tag: "p",
                        cls: "x-form-item"
                    },
                    html: "Select a zipped shapefile for uploading. The shapefile needs to have a line geometry."
                }, {
                    xtype: "fileuploadfield",
                    ref: "fileField",
                    fieldLabel: "File",
                    allowBlank: false,
                    listeners: {
                        "fileselected": function(field, file) {
                            field.ownerCt.uploadButton.enable();
                        }
                    }
                }],
                buttonAlign: "center",
                buttons: [{
                    text: "Upload",
                    ref: "../uploadButton",
                    disabled: true,
                    handler: function() {
                        var file = uploadWindow.form.fileField.fileInput.dom.files[0];
                        Ext.Ajax.request({
                            method: "PUT",
                            url: this.target.localGeoServerUrl + "rest/workspaces/" +
                                this.temporaryWorkspace + "/datastores/" +
                                file.fileName + "/file.shp?update=overwrite",
                            xmlData: file,
                            headers: {
                                "Content-Type": file.fileName.split(".").pop().toLowerCase() == "zip" ?
                                    "application/zip" : file.type
                            },
                            success: this.handleUpload.createDelegate(this,
                                [file.fileName, uploadWindow], true),
                            scope: this
                        });
                    },
                    scope: this
                }]
            }]
        });
        uploadWindow.show();
    },

    handleUpload: function(response, options, fileName, uploadWindow) {
        uploadWindow.close();
        var fileParts = fileName.split(".");
        fileParts.pop();
        var layerName = fileParts.join("");
        new OpenLayers.Protocol.WFS({
            version: "1.1.0",
            srsName: this.target.mapPanel.map.getProjectionObject().getCode(),
            url: this.target.localGeoServerUrl + "wfs",
            featureType: layerName,
            featureNS: this.temporaryWorkspaceNamespaceUri,
            outputFormat: "GML2"
        }).read({
            callback: function(response) {
                var extent = new OpenLayers.Bounds();
                var features = response.features, feature, date;
                for (var i=features.length-1; i>=0; --i) {
                    feature = features[i];
                    extent.extend(feature.geometry.getBounds());
                    feature.fid = null;
                    feature.state = OpenLayers.State.INSERT;
                    // convert dates
                    for (var a in feature.attributes) {
                        date = Date.parseDate(feature.attributes[a], "Y/m/d");
                        if (date) {
                            feature.attributes[a] = date.format("c");
                        }
                    }
                }
                var featureManager = this.target.tools[this.featureManager];
                featureManager.featureLayer.addFeatures(features);
                featureManager.featureStore.save();

                var featureEditor = this.target.tools[this.featureEditor];
                featureEditor.actions[1].control.activate();
                this.target.mapPanel.map.zoomToExtent(extent);
            },
            scope: this
        });
        //TODO remove uploaded layer/store/style or call GeoNode updatelayers
    }

});

Ext.preg(Folds.FoldSummaryForm.prototype.ptype, Folds.FoldSummaryForm);
<|endoftext|>


<commit_before>describe("CompanyValidation", function () {
  var companyValidation;

  beforeEach(function () {
    companyValidation = Object.create(CompanyValidation).init();
  });

  it("should be defined", function () {
    expect(companyValidation).toBeDefined();
  });

  describe("isYritystunnusValid", function () {
    it("should be defined", function () {
      expect(companyValidation.isYritystunnusValid).toBeDefined();
    });

    it("should return false for types other than string", function () {
      expect(companyValidation.isYritystunnusValid(true)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(null)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(undefined)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(123)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(/foo/)).toEqual(false);
    });

    it("returns OK for format NNNNNNN-T", function () {
      expect(companyValidation.isYritystunnusValid("1234567-8")).toEqual(true);
    });

    it("returns error for other formats than NNNNNNN-T", function () {
      expect(companyValidation.isYritystunnusValid("12345678")).toEqual(false);
      expect(companyValidation.isYritystunnusValid("foo")).toEqual(false);
    });
  });

});<commit_msg>Add initial specs for case examples<commit_after>describe("CompanyValidation", function () {
  var companyValidation;

  beforeEach(function () {
    companyValidation = Object.create(CompanyValidation).init();
  });

  it("should be defined", function () {
    expect(companyValidation).toBeDefined();
  });

  describe("isYritystunnusValid", function () {
    it("should be defined", function () {
      expect(companyValidation.isYritystunnusValid).toBeDefined();
    });

    it("should return false for types other than string", function () {
      expect(companyValidation.isYritystunnusValid(true)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(null)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(undefined)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(123)).toEqual(false);
      expect(companyValidation.isYritystunnusValid(/foo/)).toEqual(false);
    });

    it("returns OK for format NNNNNNN-T", function () {
      expect(companyValidation.isYritystunnusValid("1234567-8")).toEqual(true);
    });

    it("returns error for other formats than NNNNNNN-T", function () {
      expect(companyValidation.isYritystunnusValid("12345678")).toEqual(false);
      expect(companyValidation.isYritystunnusValid("foo")).toEqual(false);
    });

    it("0737546-2 is valid");
    it("1572860-0 is valid");
    it("1572860-9 is not valid");

  });

});<|endoftext|>


<commit_before>const jpeg = require('jpeg-js')

const {ImageResizeMethod} = require('../dist/types')
const ImageData = require('../dist/image-data').ImageData
const BrowserImage = require('../dist/browser-image').BrowserImage
const {expect, fixture, fixtureDecode, compareToFixture, testImage, enableWASM, disableWASM} = require('./utils')

const skater = fixture('source-skater.jpg')
const yosemite = fixture('source-yosemite.jpg')
const testSkater = (...args) => testImage(BrowserImage, 'source-skater.jpg', ...args)
const testYosemite = (...args) => testImage(BrowserImage, 'source-yosemite.jpg', ...args)
const testOpera = (...args) => testImage(BrowserImage, 'source-sydney.jpg', ...args)

describe('BrowserImage', () => {
  describe('._applyFormat', () => {
    it('should support jpeg', () => {
      const modify = img => img.format({type: 'jpeg', quality: 50})
      return testSkater('skater-poor.jpg', modify, {
        strict: false,
        tolerance: 10,
      })
    })

    it('should support png', () => {
      const modify = img => img.format('png')
      return testSkater('skater.png', modify, {
        strict: false,
        tolerance: 5,
      })
    })
  })

  describe('._applyResize', () => {
    it('should support subselect', () => {
      const modify = img =>
        img.resize({
          width: 100,
          height: 80,
          fit: 'exact',
          subselect: {
            top: 200,
            bottom: 800,
            left: 100,
            right: 700,
          },
        })

      return testYosemite('yosemite-subselect.jpg', modify, {
        strict: false,
        tolerance: 35,
      })
    })

    it('should resize with bilinear', () => {
      const modify = img =>
        img.resize({
          width: 600,
          height: 750,
          method: 'bilinear',
        })
      return testYosemite('yosemite-bilinear-minor.jpg', modify)
    })

    it('should support cover', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'cover',
        })

      return testYosemite('yosemite-square-cover.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should support contain', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'contain',
        })

      return testYosemite('yosemite-square-contain.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should support crop', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'crop',
        })

      return testOpera('opera-square-crop.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should support exact', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'exact',
        })

      return testOpera('opera-square-exact.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })
  })

  describe('._applyGreyscale', () => {
    it('should covert to greyscale', () => {
      const modify = img => img.greyscale()
      return testSkater('skater-greyscale.jpg', modify)
    })
  })

  describe('._applyCalibrate', () => {
    it('should apply a calibration profile', async () => {
      const modify = img => img.calibrate({
        redHueShift: -0.5,
        redSaturationShift: 0.5,
        greenHueShift: 0.5,
        greenSaturationShift: -0.5,
        blueHueShift: 0.5,
        blueSaturationShift: 0.5,
      })

      await testSkater('skater-calibrate.jpg', modify, {strict: false})
    })
  })

  describe('._applyTone', () => {
    it('should increase contrast', async () => {
      const modify = img => img.tone({contrast: 0.5})
      await testSkater('skater-contrast.jpg', modify, {strict: false})
    })

    it('should apply multiple tone adjustments', async () => {
      const modify = img => img.tone({
        whites: 30,
        highlights: -20,
        midtones: 30,
        shadows: 50,
        blacks: -20,
      })

      await testSkater('skater-tone.jpg', modify, {strict: false})
    })

    it('should apply curves', async () => {
      const modify = img => img.tone({
        curve: [[0, 50], [75, 65], [175, 185], [255, 200]]
      })

      await testSkater('skater-curves.jpg', modify, {strict: false})
    })
  })

  describe('._applySharpen', () => {
    it('should sharpen the image', () => {
      const modify = img => img.sharpen()
      return testSkater('skater-sharpen.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should sharpen the image with options', () => {
      const modify = img => img.sharpen({strength: 0.25})
      return testSkater('skater-sharpen-0.25.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })
  })

  describe('._applyEdges', () => {
    it('should find sobel edges', () => {
      const modify = img => img.edges()
      return testSkater('skater-browser-edges-sobel.jpg', modify)
    })

    it('should find canny edges', () => {
      const modify = img => img.edges('canny')
      return testSkater('skater-browser-edges-canny.jpg', modify)
    })

    it('should support options', () => {
      const modify = img =>
        img.edges({
          method: 'canny',
          radius: 3,
          blurSigma: 0,
        })
      return testSkater('skater-canny-radius-3.jpg', modify)
    })
  })

  describe('.toAnalysis', () => {
    it('should hash an image', () => {
      return BrowserImage.from(skater)
        .analyze({
          hash: {method: 'phash'},
        })
        .toAnalysis()
        .then(analysis => {
          const result = parseInt(analysis.hash, 2).toString(16)
          expect(result).to.equal('c5b7535fe4cb7000')
        })
    })

    it('should compute sharpness', () => {
      return BrowserImage.from(skater)
        .analyze({
          sharpness: {},
        })
        .toAnalysis()
        .then(analysis => {
          expect(analysis).to.have.property('sharpness')

          const sharpness = analysis.sharpness
          expect(sharpness.percentEdges).to.be.within(0.27, 0.28)
          expect(sharpness.median).to.be.within(74, 76)
          expect(sharpness.average).to.be.within(90, 95)
          expect(sharpness.upperVentileAverage).to.be.within(228, 232)
        })
    })
  })

  describe('.toMetadata', () => {
    it('should compute the metadata of an image', () => {
      return BrowserImage.from(skater)
        .toMetadata()
        .then(metadata => {
          expect(metadata).to.have.property('width', 256)
          expect(metadata).to.have.property('height', 256)
          expect(metadata).to.have.property('aspectRatio', 1)
          expect(metadata).to.have.deep.property('exif.width', 256)
        })
    })

    it('should compute the metadata of a raw image', () => {
      return BrowserImage.from(fixture('source-google.nef'))
        .toMetadata()
        .then(metadata => {
          expect(metadata).to.have.property('width', 4928)
          expect(metadata).to.have.property('height', 3264)
          expect(metadata).to.have.deep.property('exif.fNumber', 7.1)
        })
    })

    it('should compute the metadata of a portrait image', () => {
      return BrowserImage.from(yosemite)
        .toMetadata()
        .then(metadata => {
          expect(metadata).to.have.property('width', 1080)
          expect(metadata).to.have.property('height', 1350)
          expect(metadata).to.have.property('aspectRatio', 0.8)
          expect(metadata).to.have.deep.property('exif.width', 1080)
        })
    })
  })

  describe('.toImageData', () => {
    it('should handle RGB image data', () => {
      const pixels = Buffer.from([
        // prettier:ignore
        ...[0, 0, 0, 0, 0, 0],
        ...[0, 0, 0, 0, 0, 0],
      ])

      const imageData = {
        width: 2,
        height: 2,
        channels: 3,
        colorspace: 'rgb',
        data: pixels,
      }

      return BrowserImage.from(imageData)
        .toImageData()
        .then(data => {
          expect(data).to.eql(imageData)
        })
    })

    it('should be fast', () => {
      let promise = Promise.resolve(skater)
      for (let i = 0; i < 100; i++) {
        promise = promise.then(image => BrowserImage.from(image).toImageData())
      }

      return promise.then(imageData => {
        const decoded = ImageData.normalize(jpeg.decode(skater))
        expect(imageData.data.length).to.equal(decoded.data.length)
      })
    })

    it('should generate a valid image data', () => {
      const decoded = jpeg.decode(skater)
      return BrowserImage.from(skater)
        .toImageData()
        .then(imageData => {
          expect(imageData).to.have.property('width', decoded.width)
          expect(imageData).to.have.property('height', decoded.height)
          expect(imageData)
            .to.have.property('data')
            .with.length(decoded.data.length)
        })
    })
  })

  describe('.toBuffer', () => {
    it('should output the buffer', () => {
      const image = BrowserImage.from(skater)
      return image.toBuffer().then(buffer => {
        expect(buffer).to.be.instanceOf(Buffer)
        expect(buffer.length).to.be.within(skater.length - 5000, skater.length + 5000)
      })
    })
  })

  describe('#from', () => {
    it('should return an image from image data', () => {
      const image = BrowserImage.from(jpeg.decode(skater))
      expect(image).to.be.instanceOf(BrowserImage)
    })

    it('should return an image from buffer', () => {
      const image = BrowserImage.from(skater)
      expect(image).to.be.instanceOf(BrowserImage)
    })

    it('should handle raw images', () => {
      const image = BrowserImage.from(fixture('source-google.nef'))
      expect(image).to.be.instanceOf(BrowserImage)
      return image
        .resize({
          width: 604,
          height: 400,
          method: ImageResizeMethod.NearestNeighbor,
        })
        .toBuffer()
        .then(buffer => {
          return compareToFixture(buffer, 'google.jpg', {
            strict: false,
            increment: 10,
            tolerance: 30,
          })
        })
    })
  })

  describe('Performance', () => {
    before(async () => {
      await enableWASM()
    })

    after(async () => {
      await disableWASM()
    })

    it('should not be hella slow', async () => {
      const imageData = await fixtureDecode('source-yosemite.jpg')
      const image = BrowserImage.from(imageData)
        .tone({
          contrast: 0.5,
          whites: 30,
          highlights: -20,
          midtones: 30,
          shadows: 50,
          blacks: -20,
        })
        .calibrate({
          redHueShift: -0.5,
          redSaturationShift: 0.5,
          greenHueShift: 0.5,
          greenSaturationShift: -0.5,
          blueHueShift: 0.5,
          blueSaturationShift: 0.5,
        })

      await image.toImageData()
    }).timeout(10000)
  })
})
<commit_msg>tests(image): add timeout exception<commit_after>const jpeg = require('jpeg-js')

const {ImageResizeMethod} = require('../dist/types')
const ImageData = require('../dist/image-data').ImageData
const BrowserImage = require('../dist/browser-image').BrowserImage
const {expect, fixture, fixtureDecode, compareToFixture, testImage, enableWASM, disableWASM} = require('./utils')

const skater = fixture('source-skater.jpg')
const yosemite = fixture('source-yosemite.jpg')
const testSkater = (...args) => testImage(BrowserImage, 'source-skater.jpg', ...args)
const testYosemite = (...args) => testImage(BrowserImage, 'source-yosemite.jpg', ...args)
const testOpera = (...args) => testImage(BrowserImage, 'source-sydney.jpg', ...args)

describe('BrowserImage', () => {
  describe('._applyFormat', () => {
    it('should support jpeg', () => {
      const modify = img => img.format({type: 'jpeg', quality: 50})
      return testSkater('skater-poor.jpg', modify, {
        strict: false,
        tolerance: 10,
      })
    })

    it('should support png', () => {
      const modify = img => img.format('png')
      return testSkater('skater.png', modify, {
        strict: false,
        tolerance: 5,
      })
    })
  })

  describe('._applyResize', () => {
    it('should support subselect', () => {
      const modify = img =>
        img.resize({
          width: 100,
          height: 80,
          fit: 'exact',
          subselect: {
            top: 200,
            bottom: 800,
            left: 100,
            right: 700,
          },
        })

      return testYosemite('yosemite-subselect.jpg', modify, {
        strict: false,
        tolerance: 35,
      })
    })

    it('should resize with bilinear', () => {
      const modify = img =>
        img.resize({
          width: 600,
          height: 750,
          method: 'bilinear',
        })
      return testYosemite('yosemite-bilinear-minor.jpg', modify)
    })

    it('should support cover', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'cover',
        })

      return testYosemite('yosemite-square-cover.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should support contain', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'contain',
        })

      return testYosemite('yosemite-square-contain.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should support crop', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'crop',
        })

      return testOpera('opera-square-crop.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should support exact', () => {
      const modify = img =>
        img.resize({
          width: 200,
          height: 200,
          fit: 'exact',
        })

      return testOpera('opera-square-exact.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })
  })

  describe('._applyGreyscale', () => {
    it('should covert to greyscale', () => {
      const modify = img => img.greyscale()
      return testSkater('skater-greyscale.jpg', modify)
    })
  })

  describe('._applyCalibrate', () => {
    it('should apply a calibration profile', async () => {
      const modify = img => img.calibrate({
        redHueShift: -0.5,
        redSaturationShift: 0.5,
        greenHueShift: 0.5,
        greenSaturationShift: -0.5,
        blueHueShift: 0.5,
        blueSaturationShift: 0.5,
      })

      await testSkater('skater-calibrate.jpg', modify, {strict: false})
    })
  })

  describe('._applyTone', () => {
    it('should increase contrast', async () => {
      const modify = img => img.tone({contrast: 0.5})
      await testSkater('skater-contrast.jpg', modify, {strict: false})
    })

    it('should apply multiple tone adjustments', async () => {
      const modify = img => img.tone({
        whites: 30,
        highlights: -20,
        midtones: 30,
        shadows: 50,
        blacks: -20,
      })

      await testSkater('skater-tone.jpg', modify, {strict: false})
    })

    it('should apply curves', async () => {
      const modify = img => img.tone({
        curve: [[0, 50], [75, 65], [175, 185], [255, 200]]
      })

      await testSkater('skater-curves.jpg', modify, {strict: false})
    })
  })

  describe('._applySharpen', () => {
    it('should sharpen the image', () => {
      const modify = img => img.sharpen()
      return testSkater('skater-sharpen.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })

    it('should sharpen the image with options', () => {
      const modify = img => img.sharpen({strength: 0.25})
      return testSkater('skater-sharpen-0.25.jpg', modify, {
        strict: false,
        tolerance: 25,
      })
    })
  })

  describe('._applyEdges', () => {
    it('should find sobel edges', () => {
      const modify = img => img.edges()
      return testSkater('skater-browser-edges-sobel.jpg', modify)
    })

    it('should find canny edges', () => {
      const modify = img => img.edges('canny')
      return testSkater('skater-browser-edges-canny.jpg', modify)
    })

    it('should support options', () => {
      const modify = img =>
        img.edges({
          method: 'canny',
          radius: 3,
          blurSigma: 0,
        })
      return testSkater('skater-canny-radius-3.jpg', modify)
    })
  })

  describe('.toAnalysis', () => {
    it('should hash an image', () => {
      return BrowserImage.from(skater)
        .analyze({
          hash: {method: 'phash'},
        })
        .toAnalysis()
        .then(analysis => {
          const result = parseInt(analysis.hash, 2).toString(16)
          expect(result).to.equal('c5b7535fe4cb7000')
        })
    })

    it('should compute sharpness', () => {
      return BrowserImage.from(skater)
        .analyze({
          sharpness: {},
        })
        .toAnalysis()
        .then(analysis => {
          expect(analysis).to.have.property('sharpness')

          const sharpness = analysis.sharpness
          expect(sharpness.percentEdges).to.be.within(0.27, 0.28)
          expect(sharpness.median).to.be.within(74, 76)
          expect(sharpness.average).to.be.within(90, 95)
          expect(sharpness.upperVentileAverage).to.be.within(228, 232)
        })
    })
  })

  describe('.toMetadata', () => {
    it('should compute the metadata of an image', () => {
      return BrowserImage.from(skater)
        .toMetadata()
        .then(metadata => {
          expect(metadata).to.have.property('width', 256)
          expect(metadata).to.have.property('height', 256)
          expect(metadata).to.have.property('aspectRatio', 1)
          expect(metadata).to.have.deep.property('exif.width', 256)
        })
    })

    it('should compute the metadata of a raw image', () => {
      return BrowserImage.from(fixture('source-google.nef'))
        .toMetadata()
        .then(metadata => {
          expect(metadata).to.have.property('width', 4928)
          expect(metadata).to.have.property('height', 3264)
          expect(metadata).to.have.deep.property('exif.fNumber', 7.1)
        })
    }).timeout(5000)

    it('should compute the metadata of a portrait image', () => {
      return BrowserImage.from(yosemite)
        .toMetadata()
        .then(metadata => {
          expect(metadata).to.have.property('width', 1080)
          expect(metadata).to.have.property('height', 1350)
          expect(metadata).to.have.property('aspectRatio', 0.8)
          expect(metadata).to.have.deep.property('exif.width', 1080)
        })
    })
  })

  describe('.toImageData', () => {
    it('should handle RGB image data', () => {
      const pixels = Buffer.from([
        // prettier:ignore
        ...[0, 0, 0, 0, 0, 0],
        ...[0, 0, 0, 0, 0, 0],
      ])

      const imageData = {
        width: 2,
        height: 2,
        channels: 3,
        colorspace: 'rgb',
        data: pixels,
      }

      return BrowserImage.from(imageData)
        .toImageData()
        .then(data => {
          expect(data).to.eql(imageData)
        })
    })

    it('should be fast', () => {
      let promise = Promise.resolve(skater)
      for (let i = 0; i < 100; i++) {
        promise = promise.then(image => BrowserImage.from(image).toImageData())
      }

      return promise.then(imageData => {
        const decoded = ImageData.normalize(jpeg.decode(skater))
        expect(imageData.data.length).to.equal(decoded.data.length)
      })
    })

    it('should generate a valid image data', () => {
      const decoded = jpeg.decode(skater)
      return BrowserImage.from(skater)
        .toImageData()
        .then(imageData => {
          expect(imageData).to.have.property('width', decoded.width)
          expect(imageData).to.have.property('height', decoded.height)
          expect(imageData)
            .to.have.property('data')
            .with.length(decoded.data.length)
        })
    })
  })

  describe('.toBuffer', () => {
    it('should output the buffer', () => {
      const image = BrowserImage.from(skater)
      return image.toBuffer().then(buffer => {
        expect(buffer).to.be.instanceOf(Buffer)
        expect(buffer.length).to.be.within(skater.length - 5000, skater.length + 5000)
      })
    })
  })

  describe('#from', () => {
    it('should return an image from image data', () => {
      const image = BrowserImage.from(jpeg.decode(skater))
      expect(image).to.be.instanceOf(BrowserImage)
    })

    it('should return an image from buffer', () => {
      const image = BrowserImage.from(skater)
      expect(image).to.be.instanceOf(BrowserImage)
    })

    it('should handle raw images', () => {
      const image = BrowserImage.from(fixture('source-google.nef'))
      expect(image).to.be.instanceOf(BrowserImage)
      return image
        .resize({
          width: 604,
          height: 400,
          method: ImageResizeMethod.NearestNeighbor,
        })
        .toBuffer()
        .then(buffer => {
          return compareToFixture(buffer, 'google.jpg', {
            strict: false,
            increment: 10,
            tolerance: 30,
          })
        })
    }).timeout(5000)
  })

  describe('Performance', () => {
    before(async () => {
      await enableWASM()
    })

    after(async () => {
      await disableWASM()
    })

    it('should not be hella slow', async () => {
      const imageData = await fixtureDecode('source-yosemite.jpg')
      const image = BrowserImage.from(imageData)
        .tone({
          contrast: 0.5,
          whites: 30,
          highlights: -20,
          midtones: 30,
          shadows: 50,
          blacks: -20,
        })
        .calibrate({
          redHueShift: -0.5,
          redSaturationShift: 0.5,
          greenHueShift: 0.5,
          greenSaturationShift: -0.5,
          blueHueShift: 0.5,
          blueSaturationShift: 0.5,
        })

      await image.toImageData()
    }).timeout(10000)
  })
})
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const genReportsTable = require('../tools/generate-reports-table');
const plugin = require('../index');

/**
 * This module exports a function that enrich the interactive command line and return a promise
 * @returns {Promise} - a promise that resolve when the operation is done
 */
module.exports = (icli) => {

  // Build the lists of choices
  const choicesLists = getChoices();

  const config = {
    section: 'Lambda plugin',
    cmd: 'deploy-lambdas',
    description: 'deploy lambdas',
    parameters: [{
      cmdSpec: '[lambda-identifiers...]',
      type: 'checkbox',
      choices: choicesLists.lambdaIdentifiers,
      question: {
        message: 'Which Lambdas do you want to deploy?',
        when: (answers, cmdParameterValues) => {
          return cmdParameterValues.lambdaIdentifiers.length === 0 && !cmdParameterValues.all;
        }
      }
    }, {
      cmdSpec: '--all',
      description: 'deploy all lambdas of the project',
      type: 'boolean',
    }, {
      cmdSpec: '-r, --region <region>',
      description: 'select the AWS region',
      type: 'list',
      choices: choicesLists.region,
      validationMsgLabel: 'AWS region',
      question: {
        message: 'On which AWS region do you want to deploy?'
      }
    }, {
      cmdSpec: '-e, --environment <environment>',
      description: 'select the environment',
      type: 'input',
      default: 'DEV',
      question: {
        message: 'On which environment do you want to deploy?',
        when: (answers, cmdParameterValues) => {
          return cmdParameterValues['environment'] === undefined && plugin.myrmex.getConfig('environment') === undefined;
        }
      }
    }, {
      cmdSpec: '-a, --alias <alias>',
      description: 'select the alias to apply',
      type: 'input',
      question: {
        message: 'Which alias do you want to apply?',
        when: (answers, cmdParameterValues) => {
          return cmdParameterValues['alias'] === undefined && plugin.myrmex.getConfig('lambda.alias') === undefined;
        }
      }
    }],
    execute: executeCommand
  };

  /**
   * Create the command and the prompt
   */
  return icli.createSubCommand(config);

  /**
   * Build the choices for "list" and "checkbox" parameters
   * @returns {Object} - collection of lists of choices for "list" and "checkbox" parameters
   */
  function getChoices() {
    // First, retrieve possible values for the api-identifiers parameter
    return {
      lambdaIdentifiers: () => {
        return plugin.loadLambdas()
        .then(lambdas => {
          if (!lambdas.length) {
            icli.print(icli.format.error('This project does not contain any Lambda.'));
            process.exit(1);
          }
          return _.map(lambdas, lambda => {
            return {
              value: lambda.getIdentifier(),
              name: icli.format.info(lambda.getIdentifier())
            };
          });
        });
      },
      region: [
        {
          value: 'us-east-1',
          name: icli.format.info('us-east-1') + '      US East (N. Virginia)',
          short: 'us-east-1 - US East (N. Virginia)'
        }, {
          value: 'us-west-2',
          name: icli.format.info('us-west-2') + '      US West (Oregon)',
          short: 'us-west-2 - US West (Oregon)'
        }, {
          value: 'eu-west-1',
          name: icli.format.info('eu-west-1') + '      EU (Ireland)',
          short: 'eu-west-1 - EU (Ireland)'
        }, {
          value: 'eu-central-1',
          name: icli.format.info('eu-central-1') + '   EU (Frankfurt)',
          short: 'eu-central-1 - EU (Frankfurt)'
        }, {
          value: 'ap-northeast-1',
          name: icli.format.info('ap-northeast-1') + ' Asia Pacific (Tokyo)',
          short: 'ap-northeast-1 - Asia Pacific (Tokyo)'
        }, {
          value: 'ap-southeast-1',
          name: icli.format.info('ap-southeast-2') + ' Asia Pacific (Sydney)',
          short: 'ap-southeast-2 - Asia Pacific (Sydney)'
        }
      ]
    };
  }

  /**
   * Execute the deployment
   * @param {Object} parameters - the parameters provided in the command and in the prompt
   * @returns {Promise<null>} - The execution stops here
   */
  function executeCommand(parameters) {
    if (parameters.environment === undefined) { parameters.environment = plugin.myrmex.getConfig('environment'); }
    if (parameters.alias === undefined) { parameters.alias = plugin.myrmex.getConfig('lambda.alias'); }

    return plugin.loadLambdas()
    .then(lambdas => {
      // If the parameter "all" is set, we deploy all lambdas
      if (!parameters.all) {
        lambdas = _.filter(lambdas, lambda => { return parameters.lambdaIdentifiers.indexOf(lambda.getIdentifier()) !== -1; });
      }

      icli.print();
      icli.print('Deploying ' + icli.format.info(lambdas.length) + ' Lambda(s):');
      icli.print('  AWS region: ' + icli.format.info(parameters.region));
      icli.print('  Environement (prefix for Lambdas names): ' + icli.format.info(parameters.environment || 'no environment prefix'));
      icli.print('  Alias: ' + icli.format.info(parameters.alias || 'no alias'));
      icli.print();
      icli.print('This operation may last a little');

      return Promise.map(lambdas, lambda => {
        const context = {
          alias: parameters.alias,
          environment: parameters.environment
        };
        return lambda.deploy(parameters.region, context);
      });
    })
    .then(reports => {
      icli.print(genReportsTable(reports));
    })
    .catch(e => {
      if (e.code === 'AccessDeniedException' && e.cause && e.cause.message) {
        icli.print('\n    ' + icli.format.error('Insufficient permissions to perform the action\n'));
        icli.print('The IAM user/role you are using to perform this action does not have sufficient permissions.\n');
        icli.print(e.cause.message + '\n');
        icli.print('Please update the policies of the user/role before trying again.\n');
        process.exit(1);
      }
      throw e;
    });
  }

};
<commit_msg>Add available regions<commit_after>'use strict';

const _ = require('lodash');
const genReportsTable = require('../tools/generate-reports-table');
const plugin = require('../index');

/**
 * This module exports a function that enrich the interactive command line and return a promise
 * @returns {Promise} - a promise that resolve when the operation is done
 */
module.exports = (icli) => {

  // Build the lists of choices
  const choicesLists = getChoices();

  const config = {
    section: 'Lambda plugin',
    cmd: 'deploy-lambdas',
    description: 'deploy lambdas',
    parameters: [{
      cmdSpec: '[lambda-identifiers...]',
      type: 'checkbox',
      choices: choicesLists.lambdaIdentifiers,
      question: {
        message: 'Which Lambdas do you want to deploy?',
        when: (answers, cmdParameterValues) => {
          return cmdParameterValues.lambdaIdentifiers.length === 0 && !cmdParameterValues.all;
        }
      }
    }, {
      cmdSpec: '--all',
      description: 'deploy all lambdas of the project',
      type: 'boolean',
    }, {
      cmdSpec: '-r, --region <region>',
      description: 'select the AWS region',
      type: 'list',
      choices: choicesLists.region,
      validationMsgLabel: 'AWS region',
      question: {
        message: 'On which AWS region do you want to deploy?'
      }
    }, {
      cmdSpec: '-e, --environment <environment>',
      description: 'select the environment',
      type: 'input',
      default: 'DEV',
      question: {
        message: 'On which environment do you want to deploy?',
        when: (answers, cmdParameterValues) => {
          return cmdParameterValues['environment'] === undefined && plugin.myrmex.getConfig('environment') === undefined;
        }
      }
    }, {
      cmdSpec: '-a, --alias <alias>',
      description: 'select the alias to apply',
      type: 'input',
      question: {
        message: 'Which alias do you want to apply?',
        when: (answers, cmdParameterValues) => {
          return cmdParameterValues['alias'] === undefined && plugin.myrmex.getConfig('lambda.alias') === undefined;
        }
      }
    }],
    execute: executeCommand
  };

  /**
   * Create the command and the prompt
   */
  return icli.createSubCommand(config);

  /**
   * Build the choices for "list" and "checkbox" parameters
   * @returns {Object} - collection of lists of choices for "list" and "checkbox" parameters
   */
  function getChoices() {
    // First, retrieve possible values for the api-identifiers parameter
    return {
      lambdaIdentifiers: () => {
        return plugin.loadLambdas()
        .then(lambdas => {
          if (!lambdas.length) {
            icli.print(icli.format.error('This project does not contain any Lambda.'));
            process.exit(1);
          }
          return _.map(lambdas, lambda => {
            return {
              value: lambda.getIdentifier(),
              name: icli.format.info(lambda.getIdentifier())
            };
          });
        });
      },
      region: [
        {
          value: 'us-east-1',
          name: icli.format.info('us-east-1') + '      US East (N. Virginia)',
          short: 'us-east-1 - US East (N. Virginia)'
        }, {
          value: 'us-east-2',
          name: icli.format.info('us-east-2') + '      US East (Ohio)',
          short: 'us-east-2 - US East (Ohio)'
        }, {
          value: 'us-west-1',
          name: icli.format.info('us-west-1') + '      US West (N. California)',
          short: 'us-west-1 - US West (N. California)'
        }, {
          value: 'us-west-2',
          name: icli.format.info('us-west-2') + '      US West (Oregon)',
          short: 'us-west-2 - US West (Oregon)'
        }, {
          value: 'sa-east-1',
          name: icli.format.info('sa-east-1') + '      South America (São Paulo)',
          short: 'sa-east-1 - South America (São Paulo)'
        }, {
          value: 'eu-west-1',
          name: icli.format.info('eu-west-1') + '      EU (Ireland)',
          short: 'eu-west-1 - EU (Ireland)'
        }, {
          value: 'eu-west-2',
          name: icli.format.info('eu-west-2') + '      EU (London)',
          short: 'eu-west-2 - EU (London)'
        }, {
          value: 'eu-central-1',
          name: icli.format.info('eu-central-1') + '   EU (Frankfurt)',
          short: 'eu-central-1 - EU (Frankfurt)'
        }, {
          value: 'ap-south-1',
          name: icli.format.info('ap-south-1') + ' Asia Pacific (Mumbai)',
          short: 'ap-south-1 - Asia Pacific (Mumbai)'
        }, {
          value: 'ap-northeast-1',
          name: icli.format.info('ap-northeast-1') + ' Asia Pacific (Tokyo)',
          short: 'ap-northeast-1 - Asia Pacific (Tokyo)'
        }, {
          value: 'ap-northeast-2',
          name: icli.format.info('ap-northeast-2') + ' Asia Pacific (Seoul)',
          short: 'ap-northeast-2 - Asia Pacific (Seoul)'
        }, {
          value: 'ap-southeast-1',
          name: icli.format.info('ap-southeast-1') + ' Asia Pacific (Singapore)',
          short: 'ap-southeast-1 - Asia Pacific (Singapore)'
        }, {
          value: 'ap-southeast-2',
          name: icli.format.info('ap-southeast-2') + ' Asia Pacific (Sydney)',
          short: 'ap-southeast-2 - Asia Pacific (Sydney)'
        }
      ]
    };
  }

  /**
   * Execute the deployment
   * @param {Object} parameters - the parameters provided in the command and in the prompt
   * @returns {Promise<null>} - The execution stops here
   */
  function executeCommand(parameters) {
    if (parameters.environment === undefined) { parameters.environment = plugin.myrmex.getConfig('environment'); }
    if (parameters.alias === undefined) { parameters.alias = plugin.myrmex.getConfig('lambda.alias'); }

    return plugin.loadLambdas()
    .then(lambdas => {
      // If the parameter "all" is set, we deploy all lambdas
      if (!parameters.all) {
        lambdas = _.filter(lambdas, lambda => { return parameters.lambdaIdentifiers.indexOf(lambda.getIdentifier()) !== -1; });
      }

      icli.print();
      icli.print('Deploying ' + icli.format.info(lambdas.length) + ' Lambda(s):');
      icli.print('  AWS region: ' + icli.format.info(parameters.region));
      icli.print('  Environement (prefix for Lambdas names): ' + icli.format.info(parameters.environment || 'no environment prefix'));
      icli.print('  Alias: ' + icli.format.info(parameters.alias || 'no alias'));
      icli.print();
      icli.print('This operation may last a little');

      return Promise.map(lambdas, lambda => {
        const context = {
          alias: parameters.alias,
          environment: parameters.environment
        };
        return lambda.deploy(parameters.region, context);
      });
    })
    .then(reports => {
      icli.print(genReportsTable(reports));
    })
    .catch(e => {
      if (e.code === 'AccessDeniedException' && e.cause && e.cause.message) {
        icli.print('\n    ' + icli.format.error('Insufficient permissions to perform the action\n'));
        icli.print('The IAM user/role you are using to perform this action does not have sufficient permissions.\n');
        icli.print(e.cause.message + '\n');
        icli.print('Please update the policies of the user/role before trying again.\n');
        process.exit(1);
      }
      throw e;
    });
  }

};
<|endoftext|>


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

sc_require('components/graph/plots/plot_view');

/** @class DG.CasePlotView - A plot of dots placed according to numeric values

 @extends DG.PlotView
 */
DG.CasePlotView = DG.PlotView.extend(
    /** @scope DG.CasePlotView.prototype */
    {
      dataRangeDidChange: function (iSource, iQuestion, iKey, iChanges) {
        var this_ = this,
            tPlotElementLength = this.get('plottedElements').length,
            tCases = this.getPath('model.cases'),
            tRC = this.createRenderContext();

        // iChanges can be a single index or an array of indices
        var tChanges = (SC.typeOf(iChanges) === SC.T_NUMBER ? [iChanges] : iChanges);
        tChanges = tChanges || [];
        tChanges.forEach(function (iIndex) {
          var tCase = tCases.at(iIndex);
          if (iIndex >= tPlotElementLength)
            this_.callCreateElement(tCase, iIndex, this_._createAnimationOn);
          this_.setCircleCoordinate(tRC, tCase, iIndex);
        });
        sc_super();
      },

      /**
       * Set the coordinates and other attributes of the case circle (a Rafael element in this.get('plottedElements')).
       * @param iRC {} case-invariant Render Context
       * @param iCase {DG.Case} the case data
       * @param iIndex {number} index of case in collection
       * @param iAnimate {Boolean} (optional) want changes to be animated into place?
       * @param iCallback {Function} Will be called when animation finished
       * @returns {cx {Number},cy {Number}} final coordinates or null if not defined (hidden plot element)
       */
      setCircleCoordinate: function (iRC, iCase, iIndex, iAnimate, iCallback) {
        DG.assert(iRC && iRC.xAxisView, 'incomplete render context');
        DG.assert(iCase, 'no case');
        DG.assert(DG.MathUtilities.isInIntegerRange(iIndex, 0, this.get('plottedElements').length),
            'out of bounds', 'Index for circle element');

        function dataToCoordinateWithMargin(iAxisView, iData, iMargin) {
          // position icons in view, with margin to prevent circle edge from being clipped.
          // compare to DG.AxisView.dataToCoordinate().
          var tPixelMin = iAxisView.get('pixelMin'),
              tPixelMax = iAxisView.get('pixelMax');
          if (tPixelMin < tPixelMax)
            return tPixelMin + iMargin + iData * (tPixelMax - tPixelMin - 2 * iMargin);
          else
            return tPixelMin - iMargin + iData * (tPixelMax - tPixelMin + 2 * iMargin);
        }

        var tCircle = this.get('plottedElements')[iIndex],
            tRadius = this.radiusForCircleElement(tCircle),
            tWorldCoords = this.get('model').getWorldCoords(iIndex),
            tCoordX = dataToCoordinateWithMargin(iRC.xAxisView, tWorldCoords.x, tRadius),
            tCoordY = dataToCoordinateWithMargin(iRC.yAxisView, tWorldCoords.y, tRadius),
            tIsMissingCase = SC.none(tCoordX) || SC.none(tCoordY);

        // show or hide if needed, then update if shown.
        if (this.showHidePlottedElement(tCircle, tIsMissingCase)) {
          var tAttrs = {
            cx: tCoordX, cy: tCoordY, r: tRadius, fill: iRC.calcCaseColorString(iCase),
            stroke: iRC.strokeColor, 'fill-opacity': iRC.transparency, 'stroke-opacity': iRC.strokeTransparency
          };
          this.updatePlottedElement(tCircle, tAttrs, iAnimate, iCallback);
          return {cx: tCoordX, cy: tCoordY, r: tRadius};
        }
        return null;
      },

      createElement: function (iDatum, iIndex, iAnimate) {
        var this_ = this;

        function changeCaseValues(iIndex, iWorldValues) {
          this_.get('model').setWorldCoords(iIndex, iWorldValues);
        }

        var tIsDragging = false,
            kOpaque = 1,
            tInitialTransform = null,
            tPaper = this.get('paper'),
            tCircle = tPaper.circle(tPaper.width / 2, tPaper.height / 2, this._pointRadius)
                .attr({cursor: "pointer"})
                .addClass(DG.PlotUtilities.kDotClassName)
                .hover(function (event) {  // over
                      // Note that Firefox can come through here repeatedly so we have to check for existence
                      if (!tIsDragging && SC.none(tInitialTransform)) {
                        tInitialTransform = this.transform();
                        this.animate({
                          opacity: kOpaque,
                          transform: DG.PlotUtilities.kDataHoverTransform
                        }, DG.PlotUtilities.kDataTipShowTime);
                      }
                    },
                    function (event) { // out
                      if (!tIsDragging) {
                        this.stop();
                        this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                        tInitialTransform = null;
                      }
                    })
                .mousedown(function (iEvent) {
                  this_.get('model').selectCaseByIndex(iIndex, iEvent.shiftKey);
                })
                .drag(function (dx, dy) { // continue
                      // TODO: drag all selected cases, not just this case.
                      var tWorldX = this_.get('xAxisView').coordinateToData(this.ox + dx),
                          tWorldY = this_.get('yAxisView').coordinateToData(this.oy + dy),
                          tPoint = {x: tWorldX, y: tWorldY},
                          tRC = this_.createRenderContext(),
                          tCurrTransform = this.transform();
                      if (isFinite(tPoint.x) && isFinite(tPoint.y)) {
                        // Put the element into the initial transformed state so that changing case values
                        // will not be affected by the scaling in the current transform.
                        this.transform(tInitialTransform);
                        changeCaseValues(this.index, tPoint);
                        this.transform(tCurrTransform);
                      }
                      this_.setCircleCoordinate(tRC, this_.getPath('model.cases').at(this.index), this.index);
                    },
                    function (x, y) { // begin
                      tIsDragging = true;
                      this.ox = this.attr("cx");
                      this.oy = this.attr("cy");
                      this.animate({opacity: kOpaque}, DG.PlotUtilities.kDataTipShowTime, "bounce");
                      this.toFront();
                    },
                    function () {  // end
                      this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                      tIsDragging = false;
                    })
        ;
        tCircle.index = iIndex;
        tCircle.node.setAttribute('shape-rendering', 'geometric-precision');
        return tCircle;
      },

      /**
       We may clear and draw everything from scratch if required.
       */

      drawData: function () {
        if (!this.get('paper') || !this.get('model') || !this.getPath('model.cases') || this.getPath('model.isAnimating'))
          return;

        if( this.getPath('model.isAnimating'))
          return; // Points are animating to new position

        if( !SC.none( this.get('transferredElementCoordinates'))) {
          this.animateFromTransferredElements();
          return;
        }

        this.setPath('model.isAnimating', true);

        function dataCoordinate(iValue, iMin, iMax) {
          return iMin + kMargin + iValue * (iMax - iMin - 2 * kMargin);
        }

        function createCircleDescription(iIndex) {
          var tWorldCoords = tModel.getWorldCoords(iIndex);
          return {
            type: 'circle',
            cx: dataCoordinate(tWorldCoords.x, tXPixelMin, tXPixelMax),
            cy: dataCoordinate(tWorldCoords.y, tYPixelMin, tYPixelMax),
            r: tR / 2,
            fill: 'yellow',
            stroke: tStrokeParams.strokeColor,
            'stroke-opacity': tStrokeParams.strokeTransparency,
            cursor: 'pointer'
          };
        }

        var finishPointSet = function () {
              function changeCaseValues(iIndex, iWorldValues) {
                this_.get('model').setWorldCoords(iIndex, iWorldValues);
              }

              var this_ = this,
                  tIsDragging = false,
                  kOpaque = 1,
                  tInitialTransform = null;
              tPointSet
                  .hover(function (event) {  // over
                        // Note that Firefox can come through here repeatedly so we have to check for existence
                        if (!tIsDragging && SC.none(tInitialTransform)) {
                          tInitialTransform = this.transform();
                          this.animate({
                            opacity: kOpaque,
                            transform: DG.PlotUtilities.kDataHoverTransform
                          }, DG.PlotUtilities.kDataTipShowTime);
                        }
                      },
                      function (event) { // out
                        if (!tIsDragging) {
                          this.stop();
                          this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                          tInitialTransform = null;
                        }
                      })
                  .mousedown(function (iEvent) {
                    this_.get('model').selectCaseByIndex(this.index, iEvent.shiftKey);
                  })
                  .drag(function (dx, dy) { // continue
                        // TODO: drag all selected cases, not just this case.
                        var tWorldX = this_.get('xAxisView').coordinateToData(this.ox + dx),
                            tWorldY = this_.get('yAxisView').coordinateToData(this.oy + dy),
                            tPoint = {x: tWorldX, y: tWorldY},
                            tRC = this_.createRenderContext(),
                            tCurrTransform = this.transform();
                        if (isFinite(tPoint.x) && isFinite(tPoint.y)) {
                          // Put the element into the initial transformed state so that changing case values
                          // will not be affected by the scaling in the current transform.
                          this.transform(tInitialTransform);
                          changeCaseValues(this.index, tPoint);
                          this.transform(tCurrTransform);
                        }
                        this_.setCircleCoordinate(tRC, this_.getPath('model.cases').at(this.index), this.index);
                      },
                      function (x, y) { // begin
                        tIsDragging = true;
                        this.ox = this.attr("cx");
                        this.oy = this.attr("cy");
                        this.animate({opacity: kOpaque}, DG.PlotUtilities.kDataTipShowTime, "bounce");
                        this.toFront();
                      },
                      function () {  // end
                        this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                        tIsDragging = false;
                      })
              ;
            }.bind(this),

            tXPixelMin = this.getPath('xAxisView.pixelMin'),
            tXPixelMax = this.getPath('xAxisView.pixelMax'),
            tYPixelMax = this.getPath('yAxisView.pixelMin'),
            tYPixelMin = this.getPath('yAxisView.pixelMax'),

            tPointSet,
            tPlottedElements = this.get('plottedElements'),
            tModel = this.get('model'),
            tNumCases = tModel.get('cases').length(),
            kMargin = 10,
            tR = this.calcPointRadius(),
            tColor = tModel.getPointColor ? tModel.getPointColor() : DG.PlotUtilities.kDefaultPointColor,
            tStrokeParams = this.getStrokeParams();
        if (!tPlottedElements || tPlottedElements.length === 0) {
          var tStart = 0,
              tInc = 200,
              i,
              loop = function () {
                var tDescriptions = [];
                tXPixelMax = this.getPath('xAxisView.pixelMax');
                tYPixelMax = this.getPath('yAxisView.pixelMin');
                if( tXPixelMax < 30 || tYPixelMax < 30) {
                  this.invokeLater( loop, 100);
                  return;
                }
                for (i = tStart; i < tStart + tInc && i < tNumCases; i++) {
                  tDescriptions.push(createCircleDescription(i));
                }
                tPointSet = this.get('paper').add(tDescriptions).forEach(function (iElement, iIndex) {
                  tPlottedElements.push(iElement);
                  iElement.addClass(DG.PlotUtilities.kDotClassName);
                  iElement.index = iIndex;
                  iElement.node.setAttribute('shape-rendering', 'geometric-precision');
                  return true;
                });
                finishPointSet();
                this.setPath('model.isAnimating', true);
                tPointSet.animate({
                      r: tR,
                      fill: tColor
                    },
                    DG.PlotUtilities.kDefaultAnimationTime, '<>');
                tStart += tInc;
                tInc *= 2;
                if (tStart < tNumCases) {
                  this.invokeLater(loop, 10);
                }
                else {
                  this.setPath('model.isAnimating', false);
                }
                tPointSet.clear();
              }.bind(this);
          loop();
        }
        else if (tPlottedElements.length > 0 && !this.getPath('model.isAnimating')) {
          tPlottedElements.forEach(function (iCircle, iIndex) {
            var tCoords = tModel.getWorldCoords(iIndex);
            iCircle.attr({
              cx: dataCoordinate(tCoords.x, tXPixelMin, tXPixelMax),
              cy: dataCoordinate(tCoords.y, tYPixelMin, tYPixelMax)
            });
          });
        }
      },

      /**
       Generate the svg needed to display the plot
       */
      doDraw: function doDraw() {
        sc_super();

        this.drawData();

        this.updateSelection();

      },

      /**
       * The array object my model uses to store world coords has been swapped out. We want to animate to new positions.
       */
      worldValuesChanged: function () {
        this.prepareForConfigurationChange();
        this.set('transferredElementCoordinates', this.get('cachedPointCoordinates'));
        this._isRenderingValid = false;
        this.displayDidChange();
      }.observes('.model.worldValues')

    });

<commit_msg>[#164832875] Bug fix: Case plot isn't rescaling when graph component is resized.<commit_after>// ==========================================================================
//                          DG.CasePlotView
//
//  Author:   William Finzer
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. All rights reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('components/graph/plots/plot_view');

/** @class DG.CasePlotView - A plot of dots placed according to numeric values

 @extends DG.PlotView
 */
DG.CasePlotView = DG.PlotView.extend(
    /** @scope DG.CasePlotView.prototype */
    {
      dataRangeDidChange: function (iSource, iQuestion, iKey, iChanges) {
        var this_ = this,
            tPlotElementLength = this.get('plottedElements').length,
            tCases = this.getPath('model.cases'),
            tRC = this.createRenderContext();

        // iChanges can be a single index or an array of indices
        var tChanges = (SC.typeOf(iChanges) === SC.T_NUMBER ? [iChanges] : iChanges);
        tChanges = tChanges || [];
        tChanges.forEach(function (iIndex) {
          var tCase = tCases.at(iIndex);
          if (iIndex >= tPlotElementLength)
            this_.callCreateElement(tCase, iIndex, this_._createAnimationOn);
          this_.setCircleCoordinate(tRC, tCase, iIndex);
        });
        sc_super();
      },

      /**
       * Set the coordinates and other attributes of the case circle (a Rafael element in this.get('plottedElements')).
       * @param iRC {} case-invariant Render Context
       * @param iCase {DG.Case} the case data
       * @param iIndex {number} index of case in collection
       * @param iAnimate {Boolean} (optional) want changes to be animated into place?
       * @param iCallback {Function} Will be called when animation finished
       * @returns {cx {Number},cy {Number}} final coordinates or null if not defined (hidden plot element)
       */
      setCircleCoordinate: function (iRC, iCase, iIndex, iAnimate, iCallback) {
        DG.assert(iRC && iRC.xAxisView, 'incomplete render context');
        DG.assert(iCase, 'no case');
        DG.assert(DG.MathUtilities.isInIntegerRange(iIndex, 0, this.get('plottedElements').length),
            'out of bounds', 'Index for circle element');

        function dataToCoordinateWithMargin(iAxisView, iData, iMargin) {
          // position icons in view, with margin to prevent circle edge from being clipped.
          // compare to DG.AxisView.dataToCoordinate().
          var tPixelMin = iAxisView.get('pixelMin'),
              tPixelMax = iAxisView.get('pixelMax');
          if (tPixelMin < tPixelMax)
            return tPixelMin + iMargin + iData * (tPixelMax - tPixelMin - 2 * iMargin);
          else
            return tPixelMin - iMargin + iData * (tPixelMax - tPixelMin + 2 * iMargin);
        }

        var tCircle = this.get('plottedElements')[iIndex],
            tRadius = this.radiusForCircleElement(tCircle),
            tWorldCoords = this.get('model').getWorldCoords(iIndex),
            tCoordX = dataToCoordinateWithMargin(iRC.xAxisView, tWorldCoords.x, tRadius),
            tCoordY = dataToCoordinateWithMargin(iRC.yAxisView, tWorldCoords.y, tRadius),
            tIsMissingCase = SC.none(tCoordX) || SC.none(tCoordY);

        // show or hide if needed, then update if shown.
        if (this.showHidePlottedElement(tCircle, tIsMissingCase)) {
          var tAttrs = {
            cx: tCoordX, cy: tCoordY, r: tRadius, fill: iRC.calcCaseColorString(iCase),
            stroke: iRC.strokeColor, 'fill-opacity': iRC.transparency, 'stroke-opacity': iRC.strokeTransparency
          };
          this.updatePlottedElement(tCircle, tAttrs, iAnimate, iCallback);
          return {cx: tCoordX, cy: tCoordY, r: tRadius};
        }
        return null;
      },

      createElement: function (iDatum, iIndex, iAnimate) {
        var this_ = this;

        function changeCaseValues(iIndex, iWorldValues) {
          this_.get('model').setWorldCoords(iIndex, iWorldValues);
        }

        var tIsDragging = false,
            kOpaque = 1,
            tInitialTransform = null,
            tPaper = this.get('paper'),
            tCircle = tPaper.circle(tPaper.width / 2, tPaper.height / 2, this._pointRadius)
                .attr({cursor: "pointer"})
                .addClass(DG.PlotUtilities.kDotClassName)
                .hover(function (event) {  // over
                      // Note that Firefox can come through here repeatedly so we have to check for existence
                      if (!tIsDragging && SC.none(tInitialTransform)) {
                        tInitialTransform = this.transform();
                        this.animate({
                          opacity: kOpaque,
                          transform: DG.PlotUtilities.kDataHoverTransform
                        }, DG.PlotUtilities.kDataTipShowTime);
                      }
                    },
                    function (event) { // out
                      if (!tIsDragging) {
                        this.stop();
                        this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                        tInitialTransform = null;
                      }
                    })
                .mousedown(function (iEvent) {
                  this_.get('model').selectCaseByIndex(iIndex, iEvent.shiftKey);
                })
                .drag(function (dx, dy) { // continue
                      // TODO: drag all selected cases, not just this case.
                      var tWorldX = this_.get('xAxisView').coordinateToData(this.ox + dx),
                          tWorldY = this_.get('yAxisView').coordinateToData(this.oy + dy),
                          tPoint = {x: tWorldX, y: tWorldY},
                          tRC = this_.createRenderContext(),
                          tCurrTransform = this.transform();
                      if (isFinite(tPoint.x) && isFinite(tPoint.y)) {
                        // Put the element into the initial transformed state so that changing case values
                        // will not be affected by the scaling in the current transform.
                        this.transform(tInitialTransform);
                        changeCaseValues(this.index, tPoint);
                        this.transform(tCurrTransform);
                      }
                      this_.setCircleCoordinate(tRC, this_.getPath('model.cases').at(this.index), this.index);
                    },
                    function (x, y) { // begin
                      tIsDragging = true;
                      this.ox = this.attr("cx");
                      this.oy = this.attr("cy");
                      this.animate({opacity: kOpaque}, DG.PlotUtilities.kDataTipShowTime, "bounce");
                      this.toFront();
                    },
                    function () {  // end
                      this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                      tIsDragging = false;
                    })
        ;
        tCircle.index = iIndex;
        tCircle.node.setAttribute('shape-rendering', 'geometric-precision');
        return tCircle;
      },

      /**
       We may clear and draw everything from scratch if required.
       */

      drawData: function () {
        if (!this.get('paper') || !this.get('model') || !this.getPath('model.cases') || this.getPath('model.isAnimating'))
          return;

        if( this.getPath('model.isAnimating'))
          return; // Points are animating to new position

        if( !SC.none( this.get('transferredElementCoordinates'))) {
          this.animateFromTransferredElements();
          return;
        }

        function dataCoordinate(iValue, iMin, iMax) {
          return iMin + kMargin + iValue * (iMax - iMin - 2 * kMargin);
        }

        function createCircleDescription(iIndex) {
          var tWorldCoords = tModel.getWorldCoords(iIndex);
          return {
            type: 'circle',
            cx: dataCoordinate(tWorldCoords.x, tXPixelMin, tXPixelMax),
            cy: dataCoordinate(tWorldCoords.y, tYPixelMin, tYPixelMax),
            r: tR / 2,
            fill: 'yellow',
            stroke: tStrokeParams.strokeColor,
            'stroke-opacity': tStrokeParams.strokeTransparency,
            cursor: 'pointer'
          };
        }

        var finishPointSet = function () {
              function changeCaseValues(iIndex, iWorldValues) {
                this_.get('model').setWorldCoords(iIndex, iWorldValues);
              }

              var this_ = this,
                  tIsDragging = false,
                  kOpaque = 1,
                  tInitialTransform = null;
              tPointSet
                  .hover(function (event) {  // over
                        // Note that Firefox can come through here repeatedly so we have to check for existence
                        if (!tIsDragging && SC.none(tInitialTransform)) {
                          tInitialTransform = this.transform();
                          this.animate({
                            opacity: kOpaque,
                            transform: DG.PlotUtilities.kDataHoverTransform
                          }, DG.PlotUtilities.kDataTipShowTime);
                        }
                      },
                      function (event) { // out
                        if (!tIsDragging) {
                          this.stop();
                          this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                          tInitialTransform = null;
                        }
                      })
                  .mousedown(function (iEvent) {
                    this_.get('model').selectCaseByIndex(this.index, iEvent.shiftKey);
                  })
                  .drag(function (dx, dy) { // continue
                        // TODO: drag all selected cases, not just this case.
                        var tWorldX = this_.get('xAxisView').coordinateToData(this.ox + dx),
                            tWorldY = this_.get('yAxisView').coordinateToData(this.oy + dy),
                            tPoint = {x: tWorldX, y: tWorldY},
                            tRC = this_.createRenderContext(),
                            tCurrTransform = this.transform();
                        if (isFinite(tPoint.x) && isFinite(tPoint.y)) {
                          // Put the element into the initial transformed state so that changing case values
                          // will not be affected by the scaling in the current transform.
                          this.transform(tInitialTransform);
                          changeCaseValues(this.index, tPoint);
                          this.transform(tCurrTransform);
                        }
                        this_.setCircleCoordinate(tRC, this_.getPath('model.cases').at(this.index), this.index);
                      },
                      function (x, y) { // begin
                        tIsDragging = true;
                        this.ox = this.attr("cx");
                        this.oy = this.attr("cy");
                        this.animate({opacity: kOpaque}, DG.PlotUtilities.kDataTipShowTime, "bounce");
                        this.toFront();
                      },
                      function () {  // end
                        this.animate({transform: tInitialTransform}, DG.PlotUtilities.kHighlightHideTime);
                        tIsDragging = false;
                      })
              ;
            }.bind(this),

            tXPixelMin = this.getPath('xAxisView.pixelMin'),
            tXPixelMax = this.getPath('xAxisView.pixelMax'),
            tYPixelMax = this.getPath('yAxisView.pixelMin'),
            tYPixelMin = this.getPath('yAxisView.pixelMax'),

            tPointSet,
            tPlottedElements = this.get('plottedElements'),
            tModel = this.get('model'),
            tNumCases = tModel.get('cases').length(),
            kMargin = 10,
            tR = this.calcPointRadius(),
            tColor = tModel.getPointColor ? tModel.getPointColor() : DG.PlotUtilities.kDefaultPointColor,
            tStrokeParams = this.getStrokeParams();
        if (!tPlottedElements || tPlottedElements.length === 0) {
          var tStart = 0,
              tInc = 200,
              i,
              loop = function () {
                var tDescriptions = [];
                tXPixelMax = this.getPath('xAxisView.pixelMax');
                tYPixelMax = this.getPath('yAxisView.pixelMin');
                if( tXPixelMax < 30 || tYPixelMax < 30) {
                  this.invokeLater( loop, 100);
                  return;
                }
                for (i = tStart; i < tStart + tInc && i < tNumCases; i++) {
                  tDescriptions.push(createCircleDescription(i));
                }
                tPointSet = this.get('paper').add(tDescriptions).forEach(function (iElement, iIndex) {
                  tPlottedElements.push(iElement);
                  iElement.addClass(DG.PlotUtilities.kDotClassName);
                  iElement.index = iIndex;
                  iElement.node.setAttribute('shape-rendering', 'geometric-precision');
                  return true;
                });
                finishPointSet();
                this.setPath('model.isAnimating', true);
                tPointSet.animate({
                      r: tR,
                      fill: tColor
                    },
                    DG.PlotUtilities.kDefaultAnimationTime, '<>');
                tStart += tInc;
                tInc *= 2;
                if (tStart < tNumCases) {
                  this.invokeLater(loop, 10);
                }
                else {
                  this.setPath('model.isAnimating', false);
                }
                tPointSet.clear();
              }.bind(this);
          loop();
        }
        else if (tPlottedElements.length > 0 && !this.getPath('model.isAnimating')) {
          tPlottedElements.forEach(function (iCircle, iIndex) {
            var tCoords = tModel.getWorldCoords(iIndex);
            iCircle.attr({
              cx: dataCoordinate(tCoords.x, tXPixelMin, tXPixelMax),
              cy: dataCoordinate(tCoords.y, tYPixelMin, tYPixelMax)
            });
          });
        }
      },

      /**
       Generate the svg needed to display the plot
       */
      doDraw: function doDraw() {
        sc_super();

        this.drawData();

        this.updateSelection();

      },

      /**
       * The array object my model uses to store world coords has been swapped out. We want to animate to new positions.
       */
      worldValuesChanged: function () {
        this.prepareForConfigurationChange();
        this.set('transferredElementCoordinates', this.get('cachedPointCoordinates'));
        this._isRenderingValid = false;
        this.displayDidChange();
      }.observes('.model.worldValues')

    });

<|endoftext|>