<commit_before>import Activity from 'bepstore/models/activity';
import attr from 'ember-data/attr';

export default Activity.extend({
  engine: attr('string', { defaultValue: 'goals' }),

  /**
 * The title of the doc
 *
 * @type string
 * @public
 */
 title: attr('string'),

   /**
  * The url of the doc
  *
  * @type string
  * @public
  */
  description: attr('string'),
  progress: attr('string'),
  updateText: attr('string'),
  product_owner: attr('number'),
  head_design: attr('number'),
  head_frontend: attr('number'),
  head_backend: attr('number')
});<commit_msg>added relation to user-model<commit_after>import Activity from 'bepstore/models/activity';
import { belongsTo, hasMany } from 'ember-data/relationships';
import attr from 'ember-data/attr';
import Ember from 'ember';

const { computed: { filterBy } } = Ember;

export default Activity.extend({
  engine: attr('string', { defaultValue: 'goals' }),

  /**
 * The title of the doc
 *
 * @type string
 * @public
 */
 title: attr('string'),

   /**
  * The url of the doc
  *
  * @type string
  * @public
  */
  description: attr('string'),
  progress: attr('string'),
  updateText: attr('string'),

  contributers: hasMany('user'),

  product_owner: belongsTo('user', { inverse: null }),
  head_design: belongsTo('user', { inverse: null }),
  head_frontend: belongsTo('user', { inverse: null }),
  head_backend: belongsTo('user', { inverse: null }),

  nr_volunteers: filterBy('user').length
});
<|endoftext|>


<commit_before>import DocumentsError  from './error';
import Base from 'couch/util/assert-base';

let {
  assert,
  notBlank_,
  notBlank,
  isString_,
  isString,
  isObject_,
  isObject,
  isClass_,
  isClass,
  isFunction_,
  isFunction,
  isOneOf
} = Base(DocumentsError);

export {
  assert,
  notBlank_,
  notBlank,
  isString_,
  isString,
  isObject_,
  isObject,
  isClass_,
  isClass,
  isFunction_,
  isFunction,
  isOneOf
}

export default assert;
<commit_msg>add isArray asserts<commit_after>import DocumentsError  from './error';
import Base from 'couch/util/assert-base';

let {
  assert,
  notBlank_,
  notBlank,
  isString_,
  isString,
  isObject_,
  isObject,
  isClass_,
  isClass,
  isFunction_,
  isFunction,
  isArray_,
  isArray,
  isOneOf
} = Base(DocumentsError);

export {
  assert,
  notBlank_,
  notBlank,
  isString_,
  isString,
  isObject_,
  isObject,
  isClass_,
  isClass,
  isFunction_,
  isFunction,
  isArray_,
  isArray,
  isOneOf
}

export default assert;
<|endoftext|>


<commit_before>var Belt = require('../../lib/adapter')

//  models
  , Board = require('../lib/models/board')
  , CardLocation = require('../lib/models/cardlocation')
  , Pocket = require('../lib/models/pocket')
  , Region = require('../lib/models/region')
  , Wall = require('../lib/models/wall')

//  system
  , Commands = require('../lib/commands')
  , Queries = require('../lib/queries')
  , Services = require('../lib/services');

function Application( queue, ui, options ) {
    this.options = options || {};
    if ( !process.browser ) {
        this.options.db = require('memdown');
    }

    var belt = this.belt = new Belt( 'vuu_se', this.options );

    // initialize the services
    var services = this.services = new Services( ui, new Commands( belt ), new Queries( belt ) );

    this._listen = true;

    var factories = {
        "Board": Board
      , "CardLocation": CardLocation
      , "Pocket": Pocket
      , "Region": Region
      , "Wall": Wall
    };

    var listeners = [ 'created', 'updated', 'deleted' ];

    for ( var key in factories ) {
        var type = key.toLowerCase(), Model = factories[ key ];

        // register model
        belt.resource( type, Model.constructor )
            .schema( Model.schema )
            .validator( Model.validator )
            .beforeCreate( Model.onBeforeCreate )
            .beforeUpdate( Model.onBeforeUpdate );

        // listen to db for events
        attachListenersToDb( type );
    }

    function attachListenersToDb( type  ) {
        listeners.forEach(function( listener ) {
            belt.on( type + ':' + listener, function( data ) {
                queue.trigger( type + ':' + listener, data );
            });
        });
    }

    this.constructor = Application;

    var _this = this;

    // setup events to trigger services

    var listeners2 = [ 'new', 'create', 'edit', 'update', 'select', 'display', 'unlink', 'move', 'resize' ];

    for ( var x in factories ) {
        setUpEventListeners( x );
    }

    function setUpEventListeners( type ) {
        listeners2.forEach(function( task ) {
            queue.on( type.toLowerCase() + ':' + task, function( ev ) {
                if (!_this._listen || !services[ task + type ]) return;

                if (options.debug) {
                    console.log( 'services.' + task + type + '()' );
                }

                services[ task + type ]( ev );
            });
        });
    }

    queue
        .on('board:displayed', function( board ) {
            if (!_this._listen) return;

            services.displayCardLocations( board );
            services.displayRegions( board );
        })

        .on('wall:firsttime', function( wall ) {
            if (!_this._listen) return;

            services.newBoard();
        })

        .on('wall:created', function( wall ) {
            if (!_this._listen) return;

            services.displayWall( wall.getId() );
        })

        .on('board:created', function( board ) {
            if (!_this._listen) return;

            services.displayBoard( board.getId() );
        })

        .on('region:created', function( region ) {
            if (!_this._listen) return;

            services.displayRegion( region.getId() );
        })

        .on('cardlocation:created', function( cardlocation ) {
            if (!_this._listen) return;

            services.displayCardLocation( cardlocation.getId() );
        })

        ;
}

Application.prototype.pauseListenting = function() {
    this._listen = false;
};

Application.prototype.startListening = function() {
    this._listen = true;
};

module.exports = Application;
<commit_msg>UI interface for demo working<commit_after>var Belt = require('../../lib/adapter')

//  models
  , Board = require('../lib/models/board')
  , CardLocation = require('../lib/models/cardlocation')
  , Pocket = require('../lib/models/pocket')
  , Region = require('../lib/models/region')
  , Wall = require('../lib/models/wall')

//  system
  , Commands = require('../lib/commands')
  , Queries = require('../lib/queries')
  , Services = require('../lib/services')
  , MovementTracker = require('../lib/trackMovement');

function Application( queue, ui, options ) {
    this.options = options || {};
    if ( !process.browser ) {
        this.options.db = require('memdown');
    }

    var belt = this.belt = new Belt( 'vuu_se', this.options );

    // initialize the services
    var commands = new Commands( belt );
    var queries = new Queries( belt );
    var services = this.services = new Services( ui, commands, queries );
    var tracker = this.tracker = new MovementTracker( queue, commands, queries );

    this._listen = true;

    var factories = {
        "Board": Board
      , "CardLocation": CardLocation
      , "Pocket": Pocket
      , "Region": Region
      , "Wall": Wall
    };

    var listeners = [ 'created', 'updated', 'deleted' ];

    for ( var key in factories ) {
        var type = key.toLowerCase(), Model = factories[ key ];

        // register model
        belt.resource( type, Model.constructor )
            .schema( Model.schema )
            .validator( Model.validator )
            .beforeCreate( Model.onBeforeCreate )
            .beforeUpdate( Model.onBeforeUpdate );

        // listen to db for events
        attachListenersToDb( type );
    }

    function attachListenersToDb( type  ) {
        listeners.forEach(function( listener ) {
            belt.on( type + ':' + listener, function( data ) {
                queue.trigger( type + ':' + listener, data );
            });
        });
    }

    this.constructor = Application;

    var _this = this;

    // setup events to trigger services

    var listeners2 = [ 'new', 'create', 'edit', 'update', 'select', 'display', 'unlink', 'move', 'resize' ];

    for ( var x in factories ) {
        setUpEventListeners( x );
    }

    function setUpEventListeners( type ) {
        listeners2.forEach(function( task ) {
            queue.on( type.toLowerCase() + ':' + task, function( ev ) {
                if (!_this._listen || !services[ task + type ]) return;

                if (options.debug) {
                    console.log( 'services.' + task + type + '()' );
                }

                services[ task + type ]( ev );
            });
        });
    }

    queue
        .on('board:displayed', function( board ) {
            if (!_this._listen) return;

            services.displayCardLocations( board );
            services.displayRegions( board );
        })

        .on('wall:firsttime', function( wall ) {
            if (!_this._listen) return;

            services.newBoard();
        })

        .on('wall:created', function( wall ) {
            if (!_this._listen) return;

            services.displayWall( wall.getId() );
        })

        .on('board:created', function( board ) {
            if (!_this._listen) return;

            services.displayBoard( board.getId() );
        })

        .on('region:created', function( region ) {
            if (!_this._listen) return;

            services.displayRegion( region.getId() );
        })

        .on('cardlocation:created', function( cardlocation ) {
            if (!_this._listen) return;

            services.displayCardLocation( cardlocation );
        })

        .on( 'cardlocation:moved', function( location ) {
            if (!_this._listen) return;

            tracker.trackCardMovement( cardlocation );
        })

        .on( 'cardlocation:updated', function( location ) {
            if (!_this._listen) return;

            tracker.trackCardMovement( cardlocation );
        })

        .on( 'region:moved', function( region ) {
            if (!_this._listen) return;

            tracker.trackRegionMovement( region );
        })

        .on( 'region:updated', function( region ) {
            if (!_this._listen) return;

            tracker.trackRegionMovement( region );
        })

        ;
}

Application.prototype.pauseListenting = function() {
    this._listen = false;
};

Application.prototype.startListening = function() {
    this._listen = true;
};

module.exports = Application;
<|endoftext|>


<commit_before>/**
 * Created by barakedry on 6/21/15.
 */
/*global describe: false, it: false */

'use strict';
var MergeDiff = require('../');
var assert = require('assert');

function createBaseObject() {
    return {
        string: "string",
        number: 5,
        boolean: true,
        array: [0, 1, '2', false, {prop: 'val'}],
        obj: {
            property: "value1",
            property2: "value2"
        },
        nested: {
            a: {
                b: {
                    c: {
                        d: {
                            property: "value1",
                            property2: "value2"
                        }
                    }
                }
            }
        }
        /*
         func: function () {
         return "hello";
         }
         */
    };
}

describe("merge", function () {
    describe('merging the number 20 to the path "string"', function () {

        var merger = new MergeDiff(createBaseObject());
        var expectedObject = createBaseObject();

        expectedObject.string = 20;

        it('merged object should be the same as expected object', function () {
            merger.merge(20, 'string');
            assert.deepEqual(merger.object, expectedObject);
        });

    });

    describe('merging the string "test" to 3 existing properties with no path (to root)', function () {

        var merger = new MergeDiff(createBaseObject());
        var originalObject = createBaseObject();

        it('merged object should be the same as expected object', function () {

            merger.merge({
                string: "test",
                boolean: "test",
                number: "test"
            });

            assert.equal(merger.object.string, 'test');
            assert.equal(merger.object.boolean, 'test');
            assert.equal(merger.object.number, 'test');
            assert.notEqual(merger.object.array, 'test');
            assert.deepEqual(merger.object.nested, originalObject.nested);
        });

    });

    describe('merging 2 string properties and a new object with no path (to root)', function () {

        var merger = new MergeDiff(createBaseObject());

        it('merged object should be the same as expected object', function () {

            merger.merge({
                string: "test",
                boolean: false,
                newobject: {"test": "test"}
            });

            assert.equal(merger.get('string'), 'test');
            assert.equal(merger.object.boolean, false);
            assert.deepEqual(merger.object.newobject, {test: 'test'});
        });

    });

    describe('merging 4 items to array existing, modified, new and deleted', function () {

        var merger = new MergeDiff(createBaseObject());

        it('1 item should be modified 1 deleted and one added', function () {

            merger.on("array", function (diff) {
                assert.deepEqual(diff.differences, {'2': 'test', 4: merger.options.deleteKeyword, 7: "test2"});
            });

            merger.merge({
                array: {1: 1, '2': 'test', 4: merger.options.deleteKeyword, 7: "test2"}
            });

            //[0, 1, '2', false, {prop: 'val'}]
            //[0, 1, 'test', false, null, null, 'test2]

            assert.deepEqual(merger.object.array, [0, 1, 'test', false, , , 'test2']);
        });

    });

    describe('delete a string and boolean', function () {

        var merger = new MergeDiff(createBaseObject());

        it('merged object should be the same as expected object', function () {

            merger.merge({
                boolean: merger.options.deleteKeyword,
                string: merger.options.deleteKeyword
            });

        });

    });

    describe('merge nested object to "nested.a"', function () {

        var merger = new MergeDiff(createBaseObject());

        it('merged object should be the same as expected object', function () {

            merger.on("nested", function (diff) {
                console.log('diff', diff.differences);
            });

            merger.merge({
                b: {'added': 'asd', c: {d: {property2: undefined}}}
            }, 'nested.a');

            //[0, 1, '2', false, {prop: 'val'}]
            //[0, 1, 'test', false, null, null, 'test2]

            //expect(merger.object).property('array').to.deep.equal([0, 1, 'test', false, undefined, undefined, 'test2']);
        });

    });

});

describe("override", function () {});

describe("delete", function () {
    describe('primitives', function () {

        function createPrimitives() {
            return {
                boolean: true,
                number: 5,
                string: 'hello',
                'null': null
            };
        }

        describe('delete 1 primitive', function () {
            var merger = new MergeDiff(createPrimitives());
            //assert()
        });

    });
});

describe("differences events", function () {

});
<commit_msg>add delete test cases<commit_after>/**
 * Created by barakedry on 6/21/15.
 */
/*global describe: false, it: false */

'use strict';
var MergeDiff = require('../');
var assert = require('assert');

describe("merge-diff", function () {
    function createBaseObject() {
        return {
            string: "string",
            number: 5,
            boolean: true,
            array: [0, 1, '2', false, {prop: 'val'}],
            obj: {
                property: "value1",
                property2: "value2"
            },
            nested: {
                a: {
                    b: {
                        c: {
                            d: {
                                property: "value1",
                                property2: "value2"
                            }
                        }
                    }
                }
            }
            /*
             func: function () {
             return "hello";
             }
             */
        };
    }

    describe("merge", function () {
        describe('merging the number 20 to the path "string"', function () {

            var merger = new MergeDiff(createBaseObject());
            var expectedObject = createBaseObject();

            expectedObject.string = 20;

            it('merged object should be the same as expected object', function () {
                merger.merge(20, 'string');
                assert.deepEqual(merger.object, expectedObject);
            });

        });

        describe('merging the string "test" to 3 existing properties with no path (to root)', function () {

            var merger = new MergeDiff(createBaseObject());
            var originalObject = createBaseObject();

            it('merged object should be the same as expected object', function () {

                merger.merge({
                    string: "test",
                    boolean: "test",
                    number: "test"
                });

                assert.equal(merger.object.string, 'test');
                assert.equal(merger.object.boolean, 'test');
                assert.equal(merger.object.number, 'test');
                assert.notEqual(merger.object.array, 'test');
                assert.deepEqual(merger.object.nested, originalObject.nested);
            });

        });

        describe('merging 2 string properties and a new object with no path (to root)', function () {

            var merger = new MergeDiff(createBaseObject());

            it('merged object should be the same as expected object', function () {

                merger.merge({
                    string: "test",
                    boolean: false,
                    newobject: {"test": "test"}
                });

                assert.equal(merger.get('string'), 'test');
                assert.equal(merger.object.boolean, false);
                assert.deepEqual(merger.object.newobject, {test: 'test'});
            });

        });

        describe('merging 4 items to array existing, modified, new and deleted', function () {

            var merger = new MergeDiff(createBaseObject());

            it('1 item should be modified 1 deleted and one added', function () {

                merger.on("array", function (diff) {
                    assert.deepEqual(diff.differences, {'2': 'test', 4: merger.options.deleteKeyword, 7: "test2"});
                });

                merger.merge({
                    array: {1: 1, '2': 'test', 4: merger.options.deleteKeyword, 7: "test2"}
                });

                //[0, 1, '2', false, {prop: 'val'}]
                //[0, 1, 'test', false, null, null, 'test2]

                assert.deepEqual(merger.object.array, [0, 1, 'test', false, , , 'test2']);
            });

        });

        describe('delete a string and boolean', function () {

            var merger = new MergeDiff(createBaseObject());

            it('merged object should be the same as expected object', function () {

                merger.merge({
                    boolean: merger.options.deleteKeyword,
                    string: merger.options.deleteKeyword
                });

            });

        });

        describe('merge nested object to "nested.a"', function () {

            var merger = new MergeDiff(createBaseObject());

            it('merged object should be the same as expected object', function () {

                merger.on("nested", function (diff) {
                    console.log('diff', diff.differences);
                });

                merger.merge({
                    b: {'added': 'asd', c: {d: {property2: undefined}}}
                }, 'nested.a');

                //[0, 1, '2', false, {prop: 'val'}]
                //[0, 1, 'test', false, null, null, 'test2]

                //expect(merger.object).property('array').to.deep.equal([0, 1, 'test', false, undefined, undefined, 'test2']);
            });

        });

    });

    describe("override", function () {
    });

    describe("delete", function () {
        describe('primitives', function () {

            function createPrimitives() {
                return {
                    boolean: true,
                    number: 5,
                    string: 'hello',
                    'null': null
                };
            }

            it('delete 1 primitive', function () {
                var merger = new MergeDiff(createBaseObject());
                var expectedObject = createBaseObject();
                delete expectedObject.number;

                merger.delete('number');

                assert.deepEqual(merger.object, expectedObject);
            });

        });

        describe('nested', function () {
            it('delete 1 nested object', function () {
                var merger = new MergeDiff(createBaseObject());
                var expectedObject = createBaseObject();
                delete expectedObject.nested.a.b.c;

                merger.delete('nested.a.b.c');

                assert.deepEqual(merger.object, expectedObject);
            });
        });
    });

    describe("differences events", function () {

    });
});
<|endoftext|>


<commit_before>"use strict";

var $ = require('jquery'),
    _ = require('underscore'),
    Backbone = require('../../../shim/backbone'),
    Marionette = require('../../../shim/backbone.marionette'),
    chart = require('../../core/chart.js'),
    barChartTmpl = require('../../core/templates/barChart.html'),
    resultTmpl = require('./templates/result.html'),
    tableRowTmpl = require('./templates/tableRow.html'),
    tableTmpl = require('./templates/table.html'),
    cropTypes = require('../../core/cropTypes.json');

var ResultView = Marionette.LayoutView.extend({
    className: 'tab-pane',

    id: function() {
        return this.model.get('name');
    },

    template: resultTmpl,

    attributes: {
        role: 'tabpanel'
    },

    regions: {
        tableRegion: '.yield-table-region',
        chartRegion: '.yield-chart-region'
    },

    modelEvents: {
        'change': 'onShow'
    },

    initialize: function(options) {
        this.selectedCrops = options.scenario.get('selectedCrops');
        this.compareMode = options.compareMode;
        this.isCurrentConditions = options.scenario.get('is_current_conditions');
        this.currentConditionsModel = options.currentConditions.get('results').models[0];
    },

    onShow: function() {
        this.tableRegion.reset();
        this.chartRegion.reset();
        if (this.model.get('result') && this.currentConditionsModel.get('result')) {
            var scenarioResults = this.model.get('result'),
                currentConditionsResults = this.currentConditionsModel.get('result'),
                filteredScenarioResults = _.pick(scenarioResults, function(value, key) {
                    return value !== 0 || currentConditionsResults[key] !== 0;
                }),
                filteredCurrentConditionsResults = _.pick(currentConditionsResults, function(value, key) {
                    return value !== 0 || scenarioResults[key] !== 0;
                });
            if (this.compareMode) {
                this.chartRegion.show(new CompareChartView({
                    selectedCrops: this.selectedCrops,
                    model: this.model,
                    currentConditionsModel: this.currentConditionsModel,
                }));
            } else {
                this.tableRegion.show(new TableView({
                    scenarioResults: filteredScenarioResults,
                    currentConditionsResults: filteredCurrentConditionsResults,
                    isCurrentConditions: this.isCurrentConditions
                }));

                this.chartRegion.show(new ChartView({
                    scenarioResults: filteredScenarioResults,
                    currentConditionsResults: filteredCurrentConditionsResults,
                    isCurrentConditions: this.isCurrentConditions
                }));
            }
        }
    }
});

var TableRowView = Marionette.ItemView.extend({
    tagName: 'tr',
    template: tableRowTmpl,

    templateHelpers: function() {
        return {
            isCurrentConditions: this.options.isCurrentConditions,
        };
    }
});

var TableView = Marionette.CompositeView.extend({
    childView: TableRowView,
    childViewContainer: 'tbody',
    childViewOptions: function() {
        return {
            scenarioResults: this.options.scenarioResults,
            currentConditionsResults: this.options.currentConditionsResults,
            isCurrentConditions: this.options.isCurrentConditions
        };
    },

    template: tableTmpl,

    initialize: function() {
        this.collection = this.formatData();
        this.hasData = !_.isEmpty(this.options.scenarioResults);
    },

    onAttach: function() {
        $('[data-toggle="table"]').bootstrapTable();
    },

    templateHelpers: function() {
        return {
            isCurrentConditions: this.options.isCurrentConditions,
            hasData: this.hasData
        };
    },

    formatData: function() {
        var collection = new Backbone.Collection();

        collection.add(this.makeRowsForScenario(this.options.currentConditionsResults,
            this.options.scenarioResults));

        return collection;
    },

    makeRowsForScenario: function(currentConditionsResults, scenarioResults) {
        var self = this;
        return _.map(_.keys(currentConditionsResults), function(cropType) {
            return _.extend(self.getCropTypeValue(currentConditionsResults, scenarioResults, cropType));
        });
    },

    getCropTypeValue: function(currentConditionsResults, scenarioResults, cropType) {
        return {
            cropType: cropTypes[cropType],
            currentConditionsYield: currentConditionsResults[cropType],
            scenarioYield: scenarioResults[cropType]
        };
    }

});

var ChartView = Marionette.ItemView.extend({
    template: barChartTmpl,
    className: 'chart-container yield-chart-container',

    initialize: function(options) {
        this.compareMode = options.compareMode;
        this.hasData = !_.isEmpty(this.options.scenarioResults);
    },

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var chartEl = this.$el.find('.bar-chart').get(0),
            data = formatData(this.options.currentConditionsResults,
                this.options.scenarioResults, this.options.isCurrentConditions),
            chartOptions = {
                yAxisLabel: '% of Maximum Yield',
                yAxisUnit: '% of Maximum Yield',
                showLegend: false,
                barClasses: _.pluck(_.flatten(_.pluck(data, 'values')), 'class'),
                yAxisDomain: [0,100]
            };

        if (this.hasData) {
            chart.renderGroupedVerticalBarChart(chartEl, data, chartOptions);
        }
    },

    templateHelpers: function() {
        return {
            showDesc: true,
            hasData: this.hasData
        };
    }
});

var CompareChartView = Marionette.ItemView.extend({
    template: barChartTmpl,

    className: 'chart-container yield-chart-container',

    modelEvents: {
        'change': 'addChart'
    },

    initialize: function(options) {
        this.scenario = options.scenario;
        this.hasData = _.any(this.model.get('result'));
    },

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var chartEl = this.$el.find('.bar-chart').get(0),
            result = this.model.get('result');

        function getData(result, selectedCrops ) {
            var values = selectedCrops.map(function(cropId) {
                return {
                    x: cropTypes[cropId],
                    y: result[cropId],
                    class: 'crop-fill-' + cropId
                };
            });

            return [{values: values}];
        }

        $(chartEl).empty();
        if (result) {
            var data = getData(result, this.options.selectedCrops),
                chartOptions = {
                    yAxisLabel: '% of Maximum Yield',
                    yAxisUnit: '% of Maximum Yield',
                    barClasses: _.pluck(_.flatten(_.pluck(data, 'values')), 'class'),
                    maxBarWidth: 100,
                    margin: {top: 20, right: 0, bottom: 40, left: 40},
                    showLegend: false,
                    disableToggle: true,
                    yAxisDomain: [0, 100],
                    compareMode: true
                };

            if (this.hasData) {
                chart.renderGroupedVerticalBarChart(chartEl, data, chartOptions);
            }
        }
    },

    templateHelpers: function() {
        return {
            showDesc: false,
            hasData: this.hasData
        };
    }
});

var formatResultsFromModel = function(results, seriesName, useCurrentConditionsClass) {
    return {
        key: seriesName,
        values: _.map(results, function(value, key) {
            return {
                x: cropTypes[key],
                y: value,
                class: 'crop-fill-' + key +
                        (useCurrentConditionsClass ? ' current-conditions' : ' scenario'),
            };
        })
    };
};

var formatData = function(currentConditionsResults, scenarioResults, isCurrentConditions) {
    var data = [
        formatResultsFromModel(currentConditionsResults, "Current Crop Map",
            isCurrentConditions ? false : true),
    ];
    if (!isCurrentConditions) {
        data.push(formatResultsFromModel(scenarioResults, "This Scenario",
            false));
    }
    // Make the scenario be the leftmost bar
    return data;
};

module.exports = {
    ResultView: ResultView
};
<commit_msg>Add y axis to compare graphs<commit_after>"use strict";

var $ = require('jquery'),
    _ = require('underscore'),
    Backbone = require('../../../shim/backbone'),
    Marionette = require('../../../shim/backbone.marionette'),
    chart = require('../../core/chart.js'),
    barChartTmpl = require('../../core/templates/barChart.html'),
    resultTmpl = require('./templates/result.html'),
    tableRowTmpl = require('./templates/tableRow.html'),
    tableTmpl = require('./templates/table.html'),
    cropTypes = require('../../core/cropTypes.json');

var ResultView = Marionette.LayoutView.extend({
    className: 'tab-pane',

    id: function() {
        return this.model.get('name');
    },

    template: resultTmpl,

    attributes: {
        role: 'tabpanel'
    },

    regions: {
        tableRegion: '.yield-table-region',
        chartRegion: '.yield-chart-region'
    },

    modelEvents: {
        'change': 'onShow'
    },

    initialize: function(options) {
        this.selectedCrops = options.scenario.get('selectedCrops');
        this.compareMode = options.compareMode;
        this.isCurrentConditions = options.scenario.get('is_current_conditions');
        this.currentConditionsModel = options.currentConditions.get('results').models[0];
    },

    onShow: function() {
        this.tableRegion.reset();
        this.chartRegion.reset();
        if (this.model.get('result') && this.currentConditionsModel.get('result')) {
            var scenarioResults = this.model.get('result'),
                currentConditionsResults = this.currentConditionsModel.get('result'),
                filteredScenarioResults = _.pick(scenarioResults, function(value, key) {
                    return value !== 0 || currentConditionsResults[key] !== 0;
                }),
                filteredCurrentConditionsResults = _.pick(currentConditionsResults, function(value, key) {
                    return value !== 0 || scenarioResults[key] !== 0;
                });
            if (this.compareMode) {
                this.chartRegion.show(new CompareChartView({
                    selectedCrops: this.selectedCrops,
                    model: this.model,
                    currentConditionsModel: this.currentConditionsModel,
                }));
            } else {
                this.tableRegion.show(new TableView({
                    scenarioResults: filteredScenarioResults,
                    currentConditionsResults: filteredCurrentConditionsResults,
                    isCurrentConditions: this.isCurrentConditions
                }));

                this.chartRegion.show(new ChartView({
                    scenarioResults: filteredScenarioResults,
                    currentConditionsResults: filteredCurrentConditionsResults,
                    isCurrentConditions: this.isCurrentConditions
                }));
            }
        }
    }
});

var TableRowView = Marionette.ItemView.extend({
    tagName: 'tr',
    template: tableRowTmpl,

    templateHelpers: function() {
        return {
            isCurrentConditions: this.options.isCurrentConditions,
        };
    }
});

var TableView = Marionette.CompositeView.extend({
    childView: TableRowView,
    childViewContainer: 'tbody',
    childViewOptions: function() {
        return {
            scenarioResults: this.options.scenarioResults,
            currentConditionsResults: this.options.currentConditionsResults,
            isCurrentConditions: this.options.isCurrentConditions
        };
    },

    template: tableTmpl,

    initialize: function() {
        this.collection = this.formatData();
        this.hasData = !_.isEmpty(this.options.scenarioResults);
    },

    onAttach: function() {
        $('[data-toggle="table"]').bootstrapTable();
    },

    templateHelpers: function() {
        return {
            isCurrentConditions: this.options.isCurrentConditions,
            hasData: this.hasData
        };
    },

    formatData: function() {
        var collection = new Backbone.Collection();

        collection.add(this.makeRowsForScenario(this.options.currentConditionsResults,
            this.options.scenarioResults));

        return collection;
    },

    makeRowsForScenario: function(currentConditionsResults, scenarioResults) {
        var self = this;
        return _.map(_.keys(currentConditionsResults), function(cropType) {
            return _.extend(self.getCropTypeValue(currentConditionsResults, scenarioResults, cropType));
        });
    },

    getCropTypeValue: function(currentConditionsResults, scenarioResults, cropType) {
        return {
            cropType: cropTypes[cropType],
            currentConditionsYield: currentConditionsResults[cropType],
            scenarioYield: scenarioResults[cropType]
        };
    }

});

var ChartView = Marionette.ItemView.extend({
    template: barChartTmpl,
    className: 'chart-container yield-chart-container',

    initialize: function(options) {
        this.compareMode = options.compareMode;
        this.hasData = !_.isEmpty(this.options.scenarioResults);
    },

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var chartEl = this.$el.find('.bar-chart').get(0),
            data = formatData(this.options.currentConditionsResults,
                this.options.scenarioResults, this.options.isCurrentConditions),
            chartOptions = {
                yAxisLabel: '% of Maximum Yield',
                yAxisUnit: '% of Maximum Yield',
                showLegend: false,
                barClasses: _.pluck(_.flatten(_.pluck(data, 'values')), 'class'),
                yAxisDomain: [0,100]
            };

        if (this.hasData) {
            chart.renderGroupedVerticalBarChart(chartEl, data, chartOptions);
        }
    },

    templateHelpers: function() {
        return {
            showDesc: true,
            hasData: this.hasData
        };
    }
});

var CompareChartView = Marionette.ItemView.extend({
    template: barChartTmpl,

    className: 'chart-container yield-chart-container',

    modelEvents: {
        'change': 'addChart'
    },

    initialize: function(options) {
        this.scenario = options.scenario;
        this.hasData = _.any(this.model.get('result'));
    },

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var chartEl = this.$el.find('.bar-chart').get(0),
            result = this.model.get('result');

        function getData(result, selectedCrops ) {
            var values = selectedCrops.map(function(cropId) {
                return {
                    x: cropTypes[cropId],
                    y: result[cropId],
                    class: 'crop-fill-' + cropId
                };
            });

            return [{values: values}];
        }

        $(chartEl).empty();
        if (result) {
            var data = getData(result, this.options.selectedCrops),
                chartOptions = {
                    yAxisLabel: '% of Maximum Yield',
                    yAxisUnit: '% of Maximum Yield',
                    barClasses: _.pluck(_.flatten(_.pluck(data, 'values')), 'class'),
                    maxBarWidth: 100,
                    showLegend: false,
                    disableToggle: true,
                    yAxisDomain: [0, 100],
                    compareMode: true
                };

            if (this.hasData) {
                chart.renderGroupedVerticalBarChart(chartEl, data, chartOptions);
            }
        }
    },

    templateHelpers: function() {
        return {
            showDesc: false,
            hasData: this.hasData
        };
    }
});

var formatResultsFromModel = function(results, seriesName, useCurrentConditionsClass) {
    return {
        key: seriesName,
        values: _.map(results, function(value, key) {
            return {
                x: cropTypes[key],
                y: value,
                class: 'crop-fill-' + key +
                        (useCurrentConditionsClass ? ' current-conditions' : ' scenario'),
            };
        })
    };
};

var formatData = function(currentConditionsResults, scenarioResults, isCurrentConditions) {
    var data = [
        formatResultsFromModel(currentConditionsResults, "Current Crop Map",
            isCurrentConditions ? false : true),
    ];
    if (!isCurrentConditions) {
        data.push(formatResultsFromModel(scenarioResults, "This Scenario",
            false));
    }
    // Make the scenario be the leftmost bar
    return data;
};

module.exports = {
    ResultView: ResultView
};
<|endoftext|>


<commit_before>'use strict';

const { omit, omitBy, reduceAsync } = require('../../../utilities');
const { throwError } = require('../../../error');

const { timestampPlugin } = require('./timestamp');
const { authorPlugin } = require('./author');

// Plugins are functions that take `idl` as input,
// and returns a modified IDL as output
// They can also take an `opts` parameter
// Use cases can be: adding a attribute to each model,
// extending core functionalities, etc.
const applyPlugins = async function ({ idl }) {
  if (!idl.models) { return idl; }

  const plugins = idl.plugins && Array.isArray(idl.plugins)
    ? idl.plugins
    : [];

  // Retrieve all builtinPlugins, except the ones that have been overriden
  const defaultBuiltinPlugins = omitBy(
    builtinPlugins,
    (value, pluginName) => isOverridenPlugin({ plugins, pluginName }),
  );

  // Apply each idl.plugins as FUNC({ idl }) returning idl
  const allPlugins = [...plugins, ...Object.values(defaultBuiltinPlugins)];

  const idlWithPlugins = await reduceAsync(allPlugins, pluginReducer, idl);
  return idlWithPlugins;
};

const isOverridenPlugin = function ({ plugins, pluginName }) {
  return plugins.some(({ plugin }) => plugin === pluginName);
};

const pluginReducer = async function (idl, pluginConf, index) {
  const idlA = await applyPlugin({ idl, index, pluginConf });
  return idlA;
};

const applyPlugin = async function ({ idl, index, pluginConf }) {
  const { plugin, enabled = true, opts = {} } = getPluginConf({ pluginConf });

  // Plugins are only enabled if specified in `idl.plugins`.
  // But builtin plugins, or plugins added by other plugins,
  // need to be manually disabled if desired.
  if (!enabled) { return idl; }

  if (typeof plugin !== 'function') {
    const message = `The plugin at 'plugins[${index}]' is not a function`;
    throwError(message, { reason: 'IDL_VALIDATION' });
  }

  const idlA = await plugin({ idl, opts });
  return idlA;
};

const getPluginConf = function ({ pluginConf, pluginConf: { plugin } }) {
  // Plugin is either a function, or a string (for builtin plugins)
  if (typeof plugin !== 'string') { return pluginConf; }

  const builtinPlugin = builtinPlugins[plugin];

  if (!builtinPlugin) {
    const message = `The plugin '${plugin}' does not exist`;
    throwError(message, { reason: 'IDL_VALIDATION' });
  }

  return { ...builtinPlugin, ...omit(pluginConf, 'plugin') };
};

const builtinPlugins = {
  timestamp: {
    plugin: timestampPlugin,
  },
  author: {
    plugin: authorPlugin,
  },
};

module.exports = {
  applyPlugins,
};
<commit_msg>Remove IDL plugins after usage<commit_after>'use strict';

const { omit, omitBy, reduceAsync } = require('../../../utilities');
const { throwError } = require('../../../error');

const { timestampPlugin } = require('./timestamp');
const { authorPlugin } = require('./author');

// Plugins are functions that take `idl` as input,
// and returns a modified IDL as output
// They can also take an `opts` parameter
// Use cases can be: adding a attribute to each model,
// extending core functionalities, etc.
const applyPlugins = async function ({ idl }) {
  if (!idl.models) { return idl; }

  const plugins = idl.plugins && Array.isArray(idl.plugins)
    ? idl.plugins
    : [];

  // Retrieve all builtinPlugins, except the ones that have been overriden
  const defaultBuiltinPlugins = omitBy(
    builtinPlugins,
    (value, pluginName) => isOverridenPlugin({ plugins, pluginName }),
  );

  // Apply each idl.plugins as FUNC({ idl }) returning idl
  const allPlugins = [...plugins, ...Object.values(defaultBuiltinPlugins)];

  const idlWithPlugins = await reduceAsync(allPlugins, pluginReducer, idl);
  return omit(idlWithPlugins, 'plugins');
};

const isOverridenPlugin = function ({ plugins, pluginName }) {
  return plugins.some(({ plugin }) => plugin === pluginName);
};

const pluginReducer = async function (idl, pluginConf, index) {
  const idlA = await applyPlugin({ idl, index, pluginConf });
  return idlA;
};

const applyPlugin = async function ({ idl, index, pluginConf }) {
  const { plugin, enabled = true, opts = {} } = getPluginConf({ pluginConf });

  // Plugins are only enabled if specified in `idl.plugins`.
  // But builtin plugins, or plugins added by other plugins,
  // need to be manually disabled if desired.
  if (!enabled) { return idl; }

  if (typeof plugin !== 'function') {
    const message = `The plugin at 'plugins[${index}]' is not a function`;
    throwError(message, { reason: 'IDL_VALIDATION' });
  }

  const idlA = await plugin({ idl, opts });
  return idlA;
};

const getPluginConf = function ({ pluginConf, pluginConf: { plugin } }) {
  // Plugin is either a function, or a string (for builtin plugins)
  if (typeof plugin !== 'string') { return pluginConf; }

  const builtinPlugin = builtinPlugins[plugin];

  if (!builtinPlugin) {
    const message = `The plugin '${plugin}' does not exist`;
    throwError(message, { reason: 'IDL_VALIDATION' });
  }

  return { ...builtinPlugin, ...omit(pluginConf, 'plugin') };
};

const builtinPlugins = {
  timestamp: {
    plugin: timestampPlugin,
  },
  author: {
    plugin: authorPlugin,
  },
};

module.exports = {
  applyPlugins,
};
<|endoftext|>


<commit_before>var async = require("async"),
    fs = require("fs"),
    _ = require("underscore");

module.exports = function setupSync(model, api, options)
{
    if (model.linvoSync) return;
    model.linvoSync = true;

    var options = options || {};

    var status = function(s) {
        if (options.log) console.log("LinvoDB Sync: "+s);
    };

    var dirty = false;
    var triggerSync = _.debounce(function(cb)
    {
        dirty = true;
        q.push({}, cb);
    }, options.debounce || 500);
    model.on("updated", function(items, quiet) { if (!quiet) triggerSync() });
    model.on("inserted", function(items, quiet) { if (!quiet) triggerSync() });
    model.static("triggerSync", triggerSync);

    var mtimes = {};
    model.on("reset", function() { mtimes = {} });
    model.on("refresh", function() { triggerSync() });
    model.on("construct", function(x) { if (x._id && x._mtime) mtimes[x._id] = x._mtime });
    model.on("save", function(x) { if (x._id && x._mtime) mtimes[x._id] = x._mtime });

    /* We need to run only one task at a time */
    var q = async.queue(function(opts, cb)
    {
        if (! api.user) return cb();
        if (! dirty) return cb();

        var baseQuery = { collection: options.remoteCollection || model.modelName };
        var remote = {}, push = [], pull = [];

        var uid = api.user._id, checkUid = function() { return (api.user && api.user._id) == uid };

        status("sync started for "+model.modelName);

        async.auto({
            ensure_indexes: function(callback) { // Meaningless lookup to Ensure the DB has been indexed
                if (! checkUid()) return callback(new Error("uid changed while syncing"));
                model.count({ }, function(err, c) {
                    callback();
                }, true);
            },
            retrieve_remote: function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                api.request("datastoreMeta", baseQuery, function(err, meta)
                {
                    if (err) return callback(err);

                    meta.forEach(function(m) { remote[m[0]] = new Date(m[1]).getTime() });
                    callback();
                });
            },
            compile_changes: ["ensure_indexes", "retrieve_remote", function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                var pushIds = [];
                Object.keys(mtimes).forEach(function(id) {
                    var mtime = mtimes[id];
                    if ((remote[id] || 0) > mtime.getTime()) pull.push(id);
                    if ((remote[id] || 0) < mtime.getTime()) pushIds.push(id);
                    delete remote[id]; // already processed
                });

                pull = pull.concat(_.keys(remote)); // add all non-processed to pull queue

                model.find({ _id: { $in: pushIds } }, function(err, res) {
                    if (err) return callback(err);

                    push = res;
                    callback();
                }, true);

                // It's correct to mark the DB before commiting the changes, but when compiling the list of changes
                // Until the changes are commited, more changes might occur
                dirty = false;
            }],
            push_remote: ["compile_changes", function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                if (push.length) status("pushing "+push.length+" changes to remote for "+model.modelName);

                if (! push.length) return callback();

                api.request("datastorePut", _.extend({ }, baseQuery, { changes:
                    push.map(function(x) {
                        var item = _.extend({ }, x);
                        if (x._mtime) x._mtime = x._mtime.getTime();
                        if (x._ctime) x._ctime = x._ctime.getTime();
                        return item;
                    })
                }), callback);
            }],
            pull_local: ["compile_changes", function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                if (! pull.length) return callback();

                if (options.limitPerSync) pull = pull.slice(0, options.limitPerSync);

                api.request("datastoreGet", _.extend({ }, baseQuery, { ids: pull }), function(err, results)
                {
                    if (err) return callback(err);

                    results = _.uniq(results, "_id");

                    if (results.length) status("pulled "+results.length+" down for "+model.modelName);

                    results.forEach(function(x) {
                        x._ctime = new Date(x._ctime || 0);
                        x._mtime = new Date(x._mtime || 0);
                    });

                    if (! checkUid()) return callback(new Error("uid changed while syncing"));

                    model.save(results, function(err)
                    {
                        if (err) console.error(err);
                        callback();

                        if (results.length) model.emit("liveQueryRefresh");
                    }, true); // True for quiet mode, not emit any events
                });
            }],
            finalize: ["push_remote", "pull_local", function(callback)
            {
                status("sync finished for "+model.modelName);

                model.emit("syncFinished");

                callback();
            }]
        }, function(err) {
            if (err) console.error(err);
            cb();
        });
    }, 1);
}
<commit_msg>When pulling, de-duplicate items: optimized<commit_after>var async = require("async"),
    fs = require("fs"),
    _ = require("underscore");

module.exports = function setupSync(model, api, options)
{
    if (model.linvoSync) return;
    model.linvoSync = true;

    var options = options || {};

    var status = function(s) {
        if (options.log) console.log("LinvoDB Sync: "+s);
    };

    var dirty = false;
    var triggerSync = _.debounce(function(cb)
    {
        dirty = true;
        q.push({}, cb);
    }, options.debounce || 500);
    model.on("updated", function(items, quiet) { if (!quiet) triggerSync() });
    model.on("inserted", function(items, quiet) { if (!quiet) triggerSync() });
    model.static("triggerSync", triggerSync);

    var mtimes = {};
    model.on("reset", function() { mtimes = {} });
    model.on("refresh", function() { triggerSync() });
    model.on("construct", function(x) { if (x._id && x._mtime) mtimes[x._id] = x._mtime });
    model.on("save", function(x) { if (x._id && x._mtime) mtimes[x._id] = x._mtime });

    /* We need to run only one task at a time */
    var q = async.queue(function(opts, cb)
    {
        if (! api.user) return cb();
        if (! dirty) return cb();

        var baseQuery = { collection: options.remoteCollection || model.modelName };
        var remote = {}, push = [], pull = [];

        var uid = api.user._id, checkUid = function() { return (api.user && api.user._id) == uid };

        status("sync started for "+model.modelName);

        async.auto({
            ensure_indexes: function(callback) { // Meaningless lookup to Ensure the DB has been indexed
                if (! checkUid()) return callback(new Error("uid changed while syncing"));
                model.count({ }, function(err, c) {
                    callback();
                }, true);
            },
            retrieve_remote: function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                api.request("datastoreMeta", baseQuery, function(err, meta)
                {
                    if (err) return callback(err);

                    meta.forEach(function(m) { remote[m[0]] = new Date(m[1]).getTime() });
                    callback();
                });
            },
            compile_changes: ["ensure_indexes", "retrieve_remote", function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                var pushIds = [];
                Object.keys(mtimes).forEach(function(id) {
                    var mtime = mtimes[id];
                    if ((remote[id] || 0) > mtime.getTime()) pull.push(id);
                    if ((remote[id] || 0) < mtime.getTime()) pushIds.push(id);
                    delete remote[id]; // already processed
                });

                pull = pull.concat(_.keys(remote)); // add all non-processed to pull queue

                model.find({ _id: { $in: pushIds } }, function(err, res) {
                    if (err) return callback(err);

                    push = res;
                    callback();
                }, true);

                // It's correct to mark the DB before commiting the changes, but when compiling the list of changes
                // Until the changes are commited, more changes might occur
                dirty = false;
            }],
            push_remote: ["compile_changes", function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                if (push.length) status("pushing "+push.length+" changes to remote for "+model.modelName);

                if (! push.length) return callback();

                api.request("datastorePut", _.extend({ }, baseQuery, { changes:
                    push.map(function(x) {
                        var item = _.extend({ }, x);
                        if (x._mtime) x._mtime = x._mtime.getTime();
                        if (x._ctime) x._ctime = x._ctime.getTime();
                        return item;
                    })
                }), callback);
            }],
            pull_local: ["compile_changes", function(callback)
            {
                if (! checkUid()) return callback(new Error("uid changed while syncing"));

                if (! pull.length) return callback();

                if (options.limitPerSync) pull = pull.slice(0, options.limitPerSync);

                api.request("datastoreGet", _.extend({ }, baseQuery, { ids: pull }), function(err, results)
                {
                    if (err) return callback(err);

                    if (results.length) status("pulled "+results.length+" down for "+model.modelName);

                    var byId = { };
                    results = results.map(function(x) {
                        if (byId[x._id]) return;
                        byId[x._id] = true;
                        x._ctime = new Date(x._ctime || 0);
                        x._mtime = new Date(x._mtime || 0);
                        return x;
                    });

                    if (! checkUid()) return callback(new Error("uid changed while syncing"));

                    model.save(results, function(err)
                    {
                        if (err) console.error(err);
                        callback();

                        if (results.length) model.emit("liveQueryRefresh");
                    }, true); // True for quiet mode, not emit any events
                });
            }],
            finalize: ["push_remote", "pull_local", function(callback)
            {
                status("sync finished for "+model.modelName);

                model.emit("syncFinished");

                callback();
            }]
        }, function(err) {
            if (err) console.error(err);
            cb();
        });
    }, 1);
}
<|endoftext|>


<commit_before>goog.provide('ngeo.interaction.MeasureLengthMobile');

goog.require('ngeo.interaction.MeasureLength');
goog.require('ngeo.interaction.MobileDraw');
goog.require('ol');

/**
 * @classdesc
 * Interaction dedicated to measure length on mobile devices.
 *
 * @constructor
 * @struct
 * @extends {ngeo.interaction.MeasureLength}
 * @param {ngeox.unitPrefix} format The format function
 * @param {ngeox.interaction.MeasureOptions=} opt_options Options
 */
ngeo.interaction.MeasureLengthMobile = function(format, opt_options) {

  const options = opt_options !== undefined ? opt_options : {};

  ol.obj.assign(options, {displayHelpTooltip: false});

  ngeo.interaction.MeasureLength.call(this, format, options);

};
ol.inherits(
  ngeo.interaction.MeasureLengthMobile, ngeo.interaction.MeasureLength);

/**
 * @inheritDoc
 */
ngeo.interaction.MeasureLengthMobile.prototype.createDrawInteraction = function(style, source) {
  return new ngeo.interaction.MobileDraw({
    type: /** @type {ol.geom.GeometryType} */ ('LineString'),
    style: style,
    source: source
  });
};
<commit_msg>Add missing import<commit_after>goog.provide('ngeo.interaction.MeasureLengthMobile');

goog.require('ngeo.interaction.MeasureLength');
goog.require('ngeo.interaction.MobileDraw');
goog.require('ol');
goog.require('ol.obj');

/**
 * @classdesc
 * Interaction dedicated to measure length on mobile devices.
 *
 * @constructor
 * @struct
 * @extends {ngeo.interaction.MeasureLength}
 * @param {ngeox.unitPrefix} format The format function
 * @param {ngeox.interaction.MeasureOptions=} opt_options Options
 */
ngeo.interaction.MeasureLengthMobile = function(format, opt_options) {

  const options = opt_options !== undefined ? opt_options : {};

  ol.obj.assign(options, {displayHelpTooltip: false});

  ngeo.interaction.MeasureLength.call(this, format, options);

};
ol.inherits(
  ngeo.interaction.MeasureLengthMobile, ngeo.interaction.MeasureLength);

/**
 * @inheritDoc
 */
ngeo.interaction.MeasureLengthMobile.prototype.createDrawInteraction = function(style, source) {
  return new ngeo.interaction.MobileDraw({
    type: /** @type {ol.geom.GeometryType} */ ('LineString'),
    style: style,
    source: source
  });
};
<|endoftext|>


<commit_before>import config  from '../config';
import cron    from 'cron';
import mongo   from './mongo';
import email   from './email';
import scitran from './scitran';
import moment  from 'moment';
import url     from 'url';

let c = mongo.collections;

// public api ---------------------------------------------

let notifications = {

    /**
     * Add
     *
     * Takes a notification object and
     * adds it to the database to be processed by
     * the cron.
     */
    add (notification, callback) {
        c.crn.notifications.updateOne({_id: notification._id}, notification, {upsert: true}, callback);
    },

    /**
     * Send
     */
    send (notification, callback) {
        if (notification.type === 'email') {
            email.send(notification.email, callback);
        }
    },

    /**
     * Job Complete
     *
     * Sends an email notification to the user
     * with the status of their job.
     */
    jobComplete (job) {
        scitran.getUser(job.userId, (err, res) => {
            let user = res.body;
            notifications.add({
                _id: job.snapshotId + '_' + job.appId + '_' + job.agave.created,
                type: 'email',
                email: {
                    to: job.userId,
                    subject: 'Analysis - ' + job.appLabel + ' on ' + job.datasetLabel + ' - ' + job.agave.status,
                    template: 'job-complete',
                    data: {
                        firstName:       user.firstname,
                        lastName:        user.lastname,
                        appName:         job.appLabel,
                        appId:           job.appId,
                        jobId:           job.jobId,
                        startDate:       moment(job.agave.created).format('MMMM Do'),
                        datasetName:     job.datasetLabel,
                        status:          job.agave.status,
                        siteUrl:         url.parse(config.url).protocol + '//' + url.parse(config.url).hostname,
                        datasetId:       job.datasetId,
                        snapshotId:      job.snapshotId,
                        unsubscribeLink: ''
                    }
                }
            }, () => {});
        });
    }

};

// notifications cron -------------------------------------

new cron.CronJob('0 */1 * * * *', () => {
    c.crn.notifications.find({}).toArray((err, docs) => {
        for (let notification of docs) {
            notifications.send(notification, (err) => {
                if (!err) {
                    c.crn.notifications.removeOne({_id: notification._id}, {}, () => {});
                }
            });
        }
    });
}, null, true, 'America/Los_Angeles');

export default notifications;<commit_msg>Add logging when notifications fail<commit_after>import config  from '../config';
import cron    from 'cron';
import mongo   from './mongo';
import email   from './email';
import scitran from './scitran';
import moment  from 'moment';
import url     from 'url';

let c = mongo.collections;

// public api ---------------------------------------------

let notifications = {

    /**
     * Add
     *
     * Takes a notification object and
     * adds it to the database to be processed by
     * the cron.
     */
    add (notification, callback) {
        c.crn.notifications.updateOne({_id: notification._id}, notification, {upsert: true}, callback);
    },

    /**
     * Send
     */
    send (notification, callback) {
        if (notification.type === 'email') {
            email.send(notification.email, callback);
        }
    },

    /**
     * Job Complete
     *
     * Sends an email notification to the user
     * with the status of their job.
     */
    jobComplete (job) {
        scitran.getUser(job.userId, (err, res) => {
            let user = res.body;
            notifications.add({
                _id: job.snapshotId + '_' + job.appId + '_' + job.agave.created,
                type: 'email',
                email: {
                    to: job.userId,
                    subject: 'Analysis - ' + job.appLabel + ' on ' + job.datasetLabel + ' - ' + job.agave.status,
                    template: 'job-complete',
                    data: {
                        firstName:       user.firstname,
                        lastName:        user.lastname,
                        appName:         job.appLabel,
                        appId:           job.appId,
                        jobId:           job.jobId,
                        startDate:       moment(job.agave.created).format('MMMM Do'),
                        datasetName:     job.datasetLabel,
                        status:          job.agave.status,
                        siteUrl:         url.parse(config.url).protocol + '//' + url.parse(config.url).hostname,
                        datasetId:       job.datasetId,
                        snapshotId:      job.snapshotId,
                        unsubscribeLink: ''
                    }
                }
            }, () => {});
        });
    }

};

// notifications cron -------------------------------------

new cron.CronJob('0 */1 * * * *', () => {
    c.crn.notifications.find({}).toArray((err, docs) => {
        for (let notification of docs) {
            notifications.send(notification, (err) => {
                if (!err) {
                    c.crn.notifications.removeOne({_id: notification._id}, {}, () => {});
                } else {
                    console.log('NOTIFICATION ERROR ----------');
                    console.log(err);
                }
            });
        }
    });
}, null, true, 'America/Los_Angeles');

export default notifications;<|endoftext|>


<commit_before>;(function(window) {
  'use strict';

  var angular = window.angular;
  var localStorage = window.localStorage;

  angular
    .module('tuchong-daily')
    .service('imageLoader', [
      '$ionicLoading',
      '$timeout',
      imageLoader
    ]);

  function imageLoader($ionicLoading, $timeout) {
    var self = this;
    this.load = loadBackground;
    this.loadCache = loadCache;
    this.cache = {};

    var URL = window.URL || window.webkitURL;

    var BlobManager = {
      blobs: {},
      addBlob: function(blob) {
        var url = URL.createObjectURL(blob);
        var id = Math.random().toString(32).substr(2);

        this.blobs[id] = {
          url: url,
          blob: blob
        };

        return id;
      },

      addBlobAttr: function(id, key, value) {
        this.blobs[id][key] = value
      },

      getBlobById: function(id) {
        var blob = this.blobs[id].blob;
        blob.url = this.blobs[id].url;
        return blob;
      },

      getBlobByURL: function(url) {
        var self = this;
        var keys = Object.keys(this.blobs);

        return (function loop(ii) {
          var id = keys[ii];

          if (!id) return null;

          if (self.blobs[id].url === url) {
            var blob = self.blobs[id].blob;
            blob.url = url;
            return blob;
          }

          return loop(++ii);
        })(0);
      },

      removeBlobById: function(id) {
        var url = this.blobs[id].url;

        URL.revokeObjectURL(url);

        delete this.blobs[id];
      },

      removeBlobByURL: function(url) {
        var self = this;

        this.forEachBlobs(function(blob) {
          return blob.url === url;
        }, function(blob, id, blobs) {
          URL.revokeObjectURL(blob.url);
          delete blobs[id];
        });
      },

      forEachBlobs: function(filter, processor) {
        var self = this;
        var keys = Object.keys(this.blobs);

        (function loop(ii) {
          var id = keys[ii];

          if (!id) return;

          if (filter(self.blobs[id])) {
            return processor(self.blobs[id], id, self.blobs);
          }

          return loop(++ii);
        })(0);
      }
    };

    var ImageLoader = {
      loadImage: function(url, callback) {
        var id = null;

        var xhr = new XMLHttpRequest();
        xhr.responseType = 'arraybuffer';
        xhr.open('GET', url, true);

        xhr.onload = function(evt) {
          var arrayBuffer = new Uint8Array(this.response);
          var mime = xhr.getResponseHeader('Content-Type');
          mime = mime ? mime.split(';')[0] : 'image/jpeg';

          var blob = new Blob([arrayBuffer], {
            type: mime
          });
          id = BlobManager.addBlob(blob);
          BlobManager.addBlobAttr(id, 'originURL', url);

          callback(null, BlobManager.getBlobById(id).url, id);
        };

        xhr.onerror = function(evt) {
          return callback(evt.error);
        };

        xhr.send();
      },

      createImage: function(url, callback) {
        this.loadImage(url, function(err, blobUrl, id) {
          if (err) {
            return callback(err);
          }

          var img = new Image();
          img.src = blobUrl;

          return callback(null, img);
        });
      },

      releaseImage: function(url) {
        BlobManager.forEachBlobs(function(blob) {
          return blob.originURL === url;
        }, function(blob, id, blobs) {
          URL.revokeObjectURL(blob.url);
          delete blobs[id];
        });
      }
    };

    function loadCache(type) {
      if (!self.cache[type])
        return [];
      return self.cache[type];
    }

    // 应该将逻辑统一化
    function loadBackground(index, scope, type, id) {
      if (index === undefined || index === null || isNaN(index))
        return;

      if (scope.backgrounds[index]) {
        if (!scope.slidesReady)
          scope.slidesReady = true;

        return $ionicLoading.hide();
      }

      if (!scope.collections && !scope.images) {
        loadError();
        $timeout($ionicLoading.hide, 400);

        return;
      }

      $ionicLoading.show(
        template: ' 图片加载中...'
      );

      var image = type === 'home' ?
        scope.collections[index].images[0].uri :
        scope.images[index].uri;

      loadImage(image, loadDone, loadError);

      function loadDone() {
        scope.backgrounds[index] = image;
        scope.$apply();

        if (!scope.slidesReady)
          scope.slidesReady = true;

        // Update the whole cache
        var cacheKey = type === 'home' ? type : id;
        self.cache[cacheKey] = scope.backgrounds;

        $ionicLoading.hide();
      }

      function loadError() {
        $ionicLoading.show(
          template: '图片加载失败，请稍后再试试...'
        );
      }
    }

    function loadImage(uri, callback, onerror) {
      ImageLoader.createImage(
        uri + '.jpg',
        function(err, img) {
        img.onload = callback;
        img.onerror = onerror;
        img = null;
      });
    }
  }

})(this);
<commit_msg>Remove imageBlob loader from service ImageLoader<commit_after>;(function(window) {
  'use strict';

  var angular = window.angular;
  var localStorage = window.localStorage;

  angular
    .module('tuchong-daily')
    .service('imageLoader', [
      '$ionicLoading',
      '$timeout',
      imageLoader
    ]);

  function imageLoader($ionicLoading, $timeout) {
    var self = this;
    this.load = loadBackground;
    this.loadCache = loadCache;
    this.cache = {};

    function loadCache(type) {
      if (!self.cache[type])
        return [];
      return self.cache[type];
    }

    // 应该将逻辑统一化
    function loadBackground(index, scope, type, id) {
      if (index === undefined || index === null || isNaN(index))
        return;

      if (scope.backgrounds[index]) {
        if (!scope.slidesReady)
          scope.slidesReady = true;

        return $ionicLoading.hide();
      }

      if (!scope.collections && !scope.images) {
        loadError();
        $timeout($ionicLoading.hide, 400);

        return;
      }

      $ionicLoading.show(
        template: ' 图片加载中...'
      );

      var image = type === 'home' ?
        scope.collections[index].images[0].uri :
        scope.images[index].uri;

      loadImage(image, loadDone, loadError);

      function loadDone() {
        scope.backgrounds[index] = image;
        scope.$apply();

        if (!scope.slidesReady)
          scope.slidesReady = true;

        // Update the whole cache
        var cacheKey = type === 'home' ? type : id;
        self.cache[cacheKey] = scope.backgrounds;

        $ionicLoading.hide();
      }

      function loadError() {
        $ionicLoading.show(
          template: '图片加载失败，请稍后再试试...'
        );
      }
    }

    function loadImage(uri, callback, onerror) {
      ImageLoader.createImage(
        uri + '.jpg',
        function(err, img) {
        img.onload = callback;
        img.onerror = onerror;
        img = null;
      });
    }
  }

})(this);
<|endoftext|>


<commit_before>import classNames from "classnames/dedupe";
import PropTypes from "prop-types";
import React from "react";
import { Tooltip } from "reactjs-components";
import { Trans } from "@lingui/macro";
import { Icon } from "@dcos/ui-kit";
import { SystemIcons } from "@dcos/ui-kit/dist/packages/icons/dist/system-icons-enum";
import { iconSizeXs } from "@dcos/ui-kit/dist/packages/design-tokens/build/js/designTokens";

import PageHeaderActionsMenu from "./PageHeaderActionsMenu";

const getDropdownAction = (action, index) => {
  if (React.isValidElement(action)) {
    return action;
  }

  const { className, label, onItemSelect } = action;
  const itemClasses = classNames(className);

  return (

      <Trans id={label} render="span" />

  );
};

class PageHeaderActions extends React.Component {
  renderActionsMenu() {
    const { actions, actionsDisabled } = this.props;

    if (actions.length > 0) {
      const dropdownElements = actions.map(getDropdownAction);

      return (
        <PageHeaderActionsMenu actionsDisabled={actionsDisabled}>
          {dropdownElements}
        </PageHeaderActionsMenu>
      );
    }
  }

  renderAddButton() {
    const { addButton, actionsDisabled } = this.props;

    if (Array.isArray(addButton) && addButton.length > 0) {
      const dropdownElements = addButton.map(getDropdownAction);

      return (
        <PageHeaderActionsMenu
          iconID={SystemIcons.Plus}
          actionsDisabled={actionsDisabled}
        >
          {dropdownElements}
        </PageHeaderActionsMenu>
      );
    }

    if (addButton != null) {
      const { label, onItemSelect, className } = addButton;
      const buttonClasses = classNames(
        "button button-primary-link button-narrow",
        className
      );

      const button = (
        <button className={buttonClasses} onClick={onItemSelect}>
          <Icon
            shape={SystemIcons.Plus}
            size={iconSizeXs}
            color="currentColor"
          />
        </button>
      );

      if (label != null) {
        return <Tooltip content={label}>{button}</Tooltip>;
      }

      return button;
    }
  }

  renderSupplementalContent() {
    const { supplementalContent } = this.props;

    if (supplementalContent == null) {
      return null;
    }

    return supplementalContent;
  }

  render() {
    return (

        {this.renderSupplementalContent()}

          {this.renderAddButton()}
          {this.renderActionsMenu()}

    );
  }
}

PageHeaderActions.defaultProps = {
  actions: [],
  actionsDisabled: false
};

const classProps = PropTypes.oneOfType([
  PropTypes.array,
  PropTypes.object,
  PropTypes.string
]);

const menuActionsProps = PropTypes.shape({
  className: classProps,
  onItemSelect: PropTypes.func.isRequired,
  label: PropTypes.node.isRequired
});

PageHeaderActions.propTypes = {
  addButton: PropTypes.oneOfType([
    PropTypes.arrayOf(menuActionsProps),
    menuActionsProps
  ]),
  actions: PropTypes.arrayOf(
    PropTypes.oneOfType([PropTypes.node, menuActionsProps])
  ),
  supplementalContent: PropTypes.node,
  actionsDisabled: PropTypes.bool
};

module.exports = PageHeaderActions;
<commit_msg>chore(PageHeader): remove the noise of supplementalContent<commit_after>import classNames from "classnames/dedupe";
import PropTypes from "prop-types";
import React from "react";
import { Tooltip } from "reactjs-components";
import { Trans } from "@lingui/macro";
import { Icon } from "@dcos/ui-kit";
import { SystemIcons } from "@dcos/ui-kit/dist/packages/icons/dist/system-icons-enum";
import { iconSizeXs } from "@dcos/ui-kit/dist/packages/design-tokens/build/js/designTokens";

import PageHeaderActionsMenu from "./PageHeaderActionsMenu";

const getDropdownAction = (action, index) => {
  if (React.isValidElement(action)) {
    return action;
  }

  const { className, label, onItemSelect } = action;
  const itemClasses = classNames(className);

  return (

      <Trans id={label} render="span" />

  );
};

class PageHeaderActions extends React.Component {
  renderActionsMenu() {
    const { actions, actionsDisabled } = this.props;

    if (actions.length > 0) {
      const dropdownElements = actions.map(getDropdownAction);

      return (
        <PageHeaderActionsMenu actionsDisabled={actionsDisabled}>
          {dropdownElements}
        </PageHeaderActionsMenu>
      );
    }
  }

  renderAddButton() {
    const { addButton, actionsDisabled } = this.props;

    if (Array.isArray(addButton) && addButton.length > 0) {
      const dropdownElements = addButton.map(getDropdownAction);

      return (
        <PageHeaderActionsMenu
          iconID={SystemIcons.Plus}
          actionsDisabled={actionsDisabled}
        >
          {dropdownElements}
        </PageHeaderActionsMenu>
      );
    }

    if (addButton != null) {
      const { label, onItemSelect, className } = addButton;
      const buttonClasses = classNames(
        "button button-primary-link button-narrow",
        className
      );

      const button = (
        <button className={buttonClasses} onClick={onItemSelect}>
          <Icon
            shape={SystemIcons.Plus}
            size={iconSizeXs}
            color="currentColor"
          />
        </button>
      );

      if (label != null) {
        return <Tooltip content={label}>{button}</Tooltip>;
      }

      return button;
    }
  }

  render() {
    return (

        {this.props.supplementalContent}

          {this.renderAddButton()}
          {this.renderActionsMenu()}

    );
  }
}

PageHeaderActions.defaultProps = {
  actions: [],
  actionsDisabled: false
};

const classProps = PropTypes.oneOfType([
  PropTypes.array,
  PropTypes.object,
  PropTypes.string
]);

const menuActionsProps = PropTypes.shape({
  className: classProps,
  onItemSelect: PropTypes.func.isRequired,
  label: PropTypes.node.isRequired
});

PageHeaderActions.propTypes = {
  addButton: PropTypes.oneOfType([
    PropTypes.arrayOf(menuActionsProps),
    menuActionsProps
  ]),
  actions: PropTypes.arrayOf(
    PropTypes.oneOfType([PropTypes.node, menuActionsProps])
  ),
  supplementalContent: PropTypes.node,
  actionsDisabled: PropTypes.bool
};

module.exports = PageHeaderActions;
<|endoftext|>


<commit_before>/**
 * this class is controller of home page
 *
 * @export
 * @class tableCtrl
 */

export default class tableCtrl {
  constructor(usersListService, $timeout) {
    this.srvc = usersListService
    this.sortType = 'name'
    this.sortReverse = false
  }

  /**
   * function that decide order of sorting of column
   * if it first click on column - order of sorting will not reverse and set new OrderBy parametr
   * @param  {string} typeho
   */
  setOrder(type) {
    if (this.sortType === type) {
      this.sortReverse = !this.sortReverse
    } else {
      this.sortReverse = false
      this.sortType = type
    }
  }

  /**
   * function decide wich class aply to element decided by sorting order
   * @returns {string} class for display direction of sorting
   */
  setIcon() {
    return !this.sortReverse ? 'glyphicon glyphicon-triangle-bottom' : 'glyphicon glyphicon-triangle-top'
  }

  /**
   * function decide visibility of sort icon in column
   * @param  {string} type
   * @returns {boolean}
   */
  setShow(type) {
    return this.sortType === type
  }
}
<commit_msg>remove non used dependency<commit_after>/**
 * this class is controller of home page
 *
 * @export
 * @class tableCtrl
 */

export default class tableCtrl {
  constructor(usersListService) {
    this.srvc = usersListService
    this.sortType = 'name'
    this.sortReverse = false
  }

  /**
   * function that decide order of sorting of column
   * if it first click on column - order of sorting will not reverse and set new OrderBy parametr
   * @param  {string} typeho
   */
  setOrder(type) {
    if (this.sortType === type) {
      this.sortReverse = !this.sortReverse
    } else {
      this.sortReverse = false
      this.sortType = type
    }
  }

  /**
   * function decide wich class aply to element decided by sorting order
   * @returns {string} class for display direction of sorting
   */
  setIcon() {
    return !this.sortReverse ? 'glyphicon glyphicon-triangle-bottom' : 'glyphicon glyphicon-triangle-top'
  }

  /**
   * function decide visibility of sort icon in column
   * @param  {string} type
   * @returns {boolean}
   */
  setShow(type) {
    return this.sortType === type
  }
}
<|endoftext|>


<commit_before>'use strict'

const fs = require('fs')
const gm = require('gm').subClass({imageMagick: true})
const aws = require('aws-sdk')
const mime = require('mime')
const BUCKET = process.env.BUCKET
const s3 = new aws.S3({apiVersion: '2006-03-01'})
const assets = require('./assets')

exports.hasImage = function (Model, options) {
  const defaults = options.defaults
  const name = options.name
  const Name = name[0].toUpperCase() + name.slice(1)
  const sizes = options.sizes

  Model.prototype[`upload${Name}`] = function (file) {
    return new Upload(file, this, options).send()
  }

  Model.prototype[`${name}Key`] = function (size) {
    const ext = this[`${name}_ext`]
    return `${this.tableName}/${this.id}/${name}/${size}.${ext}`
  }

  Model.prototype[`${name}Path`] = function (size) {
    const key = this[`${name}Key`](size)
    const updatedAt = this[`${name}_updated_at`]
    if (!updatedAt) {
      return defaults ? assets.path(defaults[this.id % defaults.length]) : null
    }
    return `/assets/${key}?${+updatedAt}`
  }

  for (let size of Object.keys(sizes)) {
    Object.defineProperty(Model.prototype, size + Name, {
      get: function () {
        return this[`${name}Path`](size)
      }
    })
  }
}

class Upload {

  constructor (file, model, options) {
    this.file = file
    this.model = model
    this.name = options.name
    this.sizes = Object.assign(options.sizes, {original: null})
  }

  get ext () {
    return mime.extension(this.mimetype)
  }

  get path () {
    return this.file.path
  }

  get mimetype () {
    return this.file.mimetype
  }

  s3Key (size) {
    return `${this.model.tableName}/${this.model.id}/${this.name}/${size}.${this.ext}`
  }

  cleanup () {
    fs.unlink(this.path)
  }

  send () {
    return Promise.all(
      Object.keys(this.sizes).map(this.put.bind(this))
    ).then(() => {
      this.cleanup()
      return this.model.update({
        [`${this.name}_updated_at`]: new Date(),
        [`${this.name}_ext`]: this.ext
      })
    }).catch((e) => {
      this.cleanup()
      throw e
    })
  }

  stream (size) {
    if (size === 'original') return fs.createReadStream(this.path)
    const width = this.sizes[size]
    return gm(this.path)
      .resize(width, width, '^')
      .gravity('center')
      .crop(width, width, 0, 0)
      .noProfile()
      .stream(this.ext)
  }

  put (size) {
    return new Promise((resolve, reject) => {
      s3.upload({
        ACL: 'public-read',
        Body: this.stream(size),
        Bucket: BUCKET,
        CacheControl: `max-age=${60 * 60 * 24 * 7}, public`,
        ContentType: this.mimetype,
        Key: this.s3Key(size)
      }, (e) => e ? reject(e) : resolve())
    })
  }

}
<commit_msg>Upper case!<commit_after>'use strict'

const fs = require('fs')
const gm = require('gm').subClass({imageMagick: true})
const aws = require('aws-sdk')
const mime = require('mime')
const BUCKET = process.env.BUCKET
const s3 = new aws.S3({apiVersion: '2006-03-01'})
const assets = require('./assets')

exports.hasImage = function (Model, options) {
  const defaults = options.defaults
  const name = options.name
  const Name = name[0].toUpperCase() + name.slice(1)
  const sizes = options.sizes

  Model.prototype[`upload${Name}`] = function (file) {
    return new Upload(file, this, options).send()
  }

  Model.prototype[`${name}Key`] = function (size) {
    const ext = this[`${name}_ext`]
    return `${this.tableName}/${this.id}/${name}/${size}.${ext}`
  }

  Model.prototype[`${name}Path`] = function (size) {
    const key = this[`${name}Key`](size)
    const updatedAt = this[`${name}_updated_at`]
    if (!updatedAt) {
      return defaults ? assets.path(defaults[this.id % defaults.length]) : null
    }
    return `/assets/${key}?${+updatedAt}`
  }

  for (let size of Object.keys(sizes)) {
    Object.defineProperty(Model.prototype, size + Name, {
      get: function () {
        return this[`${name}Path`](size)
      }
    })
  }
}

class Upload {

  constructor (file, model, options) {
    this.file = file
    this.model = model
    this.name = options.name
    this.sizes = Object.assign(options.sizes, {original: null})
  }

  get ext () {
    return mime.extension(this.mimetype)
  }

  get path () {
    return this.file.path
  }

  get mimetype () {
    return this.file.mimetype
  }

  s3Key (size) {
    return `${this.model.tableName}/${this.model.id}/${this.name}/${size}.${this.ext}`
  }

  cleanup () {
    fs.unlink(this.path)
  }

  send () {
    return Promise.all(
      Object.keys(this.sizes).map(this.put.bind(this))
    ).then(() => {
      this.cleanup()
      return this.model.update({
        [`${this.name}_updated_at`]: new Date(),
        [`${this.name}_ext`]: this.ext
      })
    }).catch((e) => {
      this.cleanup()
      throw e
    })
  }

  stream (size) {
    if (size === 'original') return fs.createReadStream(this.path)
    const width = this.sizes[size]
    return gm(this.path)
      .resize(width, width, '^')
      .gravity('Center')
      .crop(width, width, 0, 0)
      .noProfile()
      .stream(this.ext)
  }

  put (size) {
    return new Promise((resolve, reject) => {
      s3.upload({
        ACL: 'public-read',
        Body: this.stream(size),
        Bucket: BUCKET,
        CacheControl: `max-age=${60 * 60 * 24 * 7}, public`,
        ContentType: this.mimetype,
        Key: this.s3Key(size)
      }, (e) => e ? reject(e) : resolve())
    })
  }

}
<|endoftext|>