<commit_before>define([
    'jquery',
    'underscore',
    'backbone',
    'app',
    'simplemde',
    'views/navigationtree/NavigationTreeView',
    'views/projects/ViewNodeItemView',
    'views/testsuites/TestSuiteView',
    'views/testcases/TestCaseView',
    'models/project/ProjectModel',
    'models/testsuite/TestSuiteModel',
    'models/testcase/TestCaseModel',
    'models/testplan/TestPlanModel',
    'models/testrun/TestRunModel',
    'collections/core/ExecutionStatusesCollection',
    'collections/navigationtree/NavigationTreeCollection',
    'collections/testruns/TestRunsCollection',
    'text!templates/testruns/viewTestRunTemplate.html',
    'text!templates/projects/projectNodeItemTemplate.html',
    'text!templates/testsuites/testSuiteNodeItemTemplate.html',
    'text!templates/testcases/testCaseExecuteNodeItemTemplate.html'
    ], function(
        $,
        _,
        Backbone,
        app,
        SimpleMDE,
        NavigationTreeView,
        ViewNodeItemView,
        TestSuiteView,
        TestCaseView,
        ProjectModel,
        TestSuiteModel,
        TestCaseModel,
        TestPlanModel,
        TestRunModel,
        ExecutionStatusesCollection,
        NavigationTreeCollection,
        TestRunsCollection,
        viewTestRunTemplate,
        projectNodeItemTemplate,
        testSuiteNodeItemTemplate,
        testCaseExecuteNodeItemTemplate) {

    /**
     * Displays the navigation tree.
     */
     var ViewTestRunView = Backbone.View.extend({
        el: $("#page"),

        events: {
            'click #save-testplan-btn': 'addTestCasesToTestPlan',
            'click #cancel-testplan-btn': 'cancelAndGoBack',
            'click #execution-history-btn': 'toggleExecutionHistory',
            'click #execute-testcase-btn': 'executeTestCase'
        },

        initialize: function(options) {
            _.bindAll(this,
                'render', 'render2',
                'setProjectId',
                'setTestSuiteId',
                'setTestCaseId',
                'setTestPlanId',
                'setTestRunId',
                'updateNavigationTree',
                'displayLoading',
                'displayProject',
                'displayTestSuite',
                'displayTestCase',
                'cancelAndGoBack',
                'toggleExecutionHistory',
                'executeTestCase');

            var self = this;

            this.projectId = parseInt(options.projectId);
            this.testSuiteId = 0;
            this.testCaseId = 0;
            this.testPlanId = parseInt(options.testPlanId);
            this.testRunId = parseInt(options.testRunId);

            // Collections
            this.navigationTreeCollection = new NavigationTreeCollection({
                projectId: self.projectId
            });
            this.testRunsCollection = new TestRunsCollection({ test_plan_id: this.testPlanId });
            this.executionStatusCollection = new ExecutionStatusesCollection();

            // Models
            this.testPlanModel = new TestPlanModel();
            this.testPlanModel.set('id', this.testPlanId);
            this.testRunModel = new TestRunModel({ test_plan_id: this.testPlanId });
            this.testRunModel.set('id', this.testPlanId);

            // Views
            this.navigationTreeView = new NavigationTreeView({
                draggable: false,
                checkboxes: false,
                rootNodeUrl: '#/testplans/' + options.testPlanId + '/testruns/' + options.testRunId + '/execute',
                nodeUrlPrefix: '#/testplans/' + options.testPlanId + '/testruns/' + options.testRunId,
                nodeUrlSuffix: '/execute',
                collection: self.navigationTreeCollection
            });
            this.viewNodeItemView = new ViewNodeItemView();
            this.testSuiteView = new TestSuiteView();
            this.testCaseView = new TestCaseView();
            this.testCaseSimplemde = null;

            // Events
            Backbone.on('nestor:navigationtree:project_changed', this.updateNavigationTree);
            Backbone.on('nestor:navigationtree_changed', this.updateNavigationTree);

            // For GC
            this.subviews = new Object();
            this.subviews.navigationTreeView = this.navigationTreeView;
            this.subviews.viewNodeItemView = this.viewNodeItemView;
            this.subviews.testSuiteView = this.testSuiteView;
            this.subviews.testCaseView = this.testCaseView;
        },

        cancelAndGoBack: function(evt) {
            Backbone.history.navigate("#/execution", { trigger: false });
        },

        render: function() {
            var self = this;
            $.when(this.testRunModel.fetch(), this.navigationTreeCollection.fetch({ reset: true }))
            .done(function() {
                self.render2();
            })
            ;
        },

        render2: function() {
            $('.item').removeClass('active');
            $('.item a[href="#/execution"]').parent().addClass('active');

            var compiledTemplate = _.template(viewTestRunTemplate, {});
            this.$el.html(compiledTemplate);

            this.$('#content-main').empty();
            this.updateNavigationTree();
            this.delegateEvents();
        },

        updateNavigationTree: function(event) {
            var self = this;
            if (app.currentView == this) {
                console.log('Rendering navigation tree!');
                this.$('#navigation-tree').fancytree({});
                self.navigationTreeView.render({
                    el: self.$('#navigation-tree'),
                    selected: self.testPlanModel.get('test_cases')
                });
            }
        },

        setProjectId: function(id) {
            var projectId = parseInt(id);
            // update project ID in models
            this.projectModel = new ProjectModel();
            this.projectModel.set('id', projectId);

            this.testSuiteModel = new TestSuiteModel();
            this.testSuiteModel.set('project_id', projectId);

            this.testCaseModel = new TestCaseModel();
            this.testCaseModel.set('project_id', projectId);

            this.navigationTreeView.projectId = projectId;

            if (this.projectId !== projectId/* || !$.trim($(this.navigationTreeView.el).html())*/) {
                this.projectId = projectId;
                Backbone.trigger('nestor:navigationtree:project_changed');
                if (app.currentView == this) {
                    this.displayProject();
                }
            }
        },

        setTestSuiteId: function(testSuiteId) {
            // update test suite ID in models
            this.testSuiteModel = new TestSuiteModel();
            this.testSuiteModel.project_id = this.projectId;
            this.testSuiteModel.id = testSuiteId;
            this.testSuiteId = testSuiteId;

            this.testCaseModel = new TestCaseModel();
            this.testCaseModel.project_id = this.projectId;
            this.testCaseModel.testsuite_id = testSuiteId;
        },

        setTestCaseId: function(testCaseId) {
            // update test suite ID in models
            this.testCaseModel = new TestCaseModel();
            this.testCaseModel.project_id = this.projectId;
            this.testCaseModel.testsuite_id = this.testSuiteId;
            this.testCaseModel.id = testCaseId;

            this.testCaseId = testCaseId;
        },

        setTestPlanId: function(testPlanId) {
            this.testPlanId = testPlanId;
            this.testPlanModel.set('id', this.testPlanId);
        },

        setTestRunId: function(testRunId) {
            this.testRunId = testRunId;
            this.testRunModel.set('id', this.testRunId);
        },

        displayLoading: function() {
            this.$('#content-main').empty();
            this.$('#content-main').html('

');
        },

        toggleExecutionHistory: function() {
            this.$("$execution-history-panel").toggle();
        },

        /**
         * Display project node item on the right panel of the screen.
         */
         displayProject: function() {
            this.displayLoading();
            $('.item').removeClass('active');
            $('.item a[href="#/execution"]').parent().addClass('active');
            var self = this;
            this.projectModel.fetch({
                success: function(responseData) {
                    var project = self.projectModel;
                    var data = {
                        project: project,
                        _: _,
                        editable: false
                    };

                    var compiledTemplate = _.template(projectNodeItemTemplate, data);
                    self.viewNodeItemView.$el.html(compiledTemplate);
                    this.$('#content-main').unbind();
                    this.$('#content-main').empty();
                    this.$('#content-main').append(self.viewNodeItemView.el);
                    self.viewNodeItemView.delegateEvents();
                    var tree = this.$('#navigation-tree').fancytree('getTree');
                    if (typeof tree !== typeof undefined && typeof tree.getNodeByKey !== typeof undefined) {
                        var node = tree.getNodeByKey("1-" + project.get('id'));
                        if (node && typeof node !== typeof undefined && typeof node.setActive !== typeof undefined) {
                            node.setActive();
                        } else {
                            console.log('Invalid tree node for node ' + project.get('id'));
                        }
                    } else {
                        console.log('Invalid navigation tree for project ' + self.projectId);
                    }
                },
                error: function() {
                    throw new Error("Failed to fetch project");
                }
            });
        },

        /**
         * Display test suite node item on the right panel of the screen.
         */
         displayTestSuite: function() {
            var self = this;
            this.testSuiteModel.set('id', this.testSuiteId);
            this.testSuiteModel.fetch({
                success: function(responseData) {
                    var data = {
                        testsuite: self.testSuiteModel,
                        _: _,
                        editable: false
                    };

                    var compiledTemplate = _.template(testSuiteNodeItemTemplate, data);
                    self.viewNodeItemView.$el.html(compiledTemplate);
                    self.$('#content-main').empty();
                    self.$('#content-main').append(self.viewNodeItemView.el);
                },
                error: function() {
                    throw new Error("Failed to fetch test suite");
                }
            });
        },

        /**
         * Display test case node item on the right panel of the screen.
         */
         displayTestCase: function() {
            var self = this;
            this.testCaseModel.set('project_id', this.projectId);
            this.testCaseModel.set('test_suite_id', this.testSuiteId);
            this.testCaseModel.set('id', this.testCaseId);
            this.testCaseModel.url = '/api/testplans/' + this.testPlanId + '/testruns/' + this.testRunId + '/testsuites/' + this.testSuiteId + '/testcases/' + this.testCaseId + '/executions';
            var self = this;
            $.when(this.testCaseModel.fetch(), this.executionStatusCollection.fetch())
                .done(function(testCaseResponse, executionStatusesResponse) {
                    var testcase = self.testCaseModel;
                    var execution_statuses = self.executionStatusCollection.models;
                    var data = {
                        testcase: testcase,
                        execution_statuses: execution_statuses,
                        _: _
                    };

                    var compiledTemplate = _.template(testCaseExecuteNodeItemTemplate, data);
                    self.viewNodeItemView.$el.html(compiledTemplate);

                    self.$('#content-main').empty();
                    self.$('#content-main').append(self.viewNodeItemView.el);
                    self.$('#navigation-tree').fancytree('getTree').getNodeByKey("3-" + testcase.get('id')).setActive();

                    self.testCaseSimplemde = new SimpleMDE({
                        autoDownloadFontAwesome: true,
                        autofocus: false,
                        autosave: {
                            enabled: false
                        },
                        element: $('#testcase-notes-input')[0],
                        indentWithTabs: false,
                        spellChecker: false,
                        tabSize: 4
                    });
                })
            ;
        },

        /**
         * Execute test case.
         */
        executeTestCase: function() {
            var self = this;
            if (this.$("#execute-testcase-form").parsley().validate()) {
                var testRun = this.collection.create({
                    notes: this.testCaseSimplemde.value(),
                    execution_statuses_id: this.$("#testcase-executionstatus_id-input").val()
                }, {
                    wait: true,
                    success: function(mod, res) {
                        app.showAlert('Success!', 'Test case ' + this.$("#testrun-name-input").val() + ' execution status updated!', 'success')
                        Backbone.history.navigate("#/testplans/" + self.testPlanId + '/testruns' + self.testRunId + '/testsuites/' + self.testSuiteId + '/testcases/' + self.testCaseId + '/execute', {
                            trigger: false
                        });
                    },
                    error: function(model, response, options) {
                        var message = _.has(response, 'statusText') ? response.statusText : 'Unknown error!';
                        if (
                            _.has(response, 'responseJSON') &&
                            _.has(response.responseJSON, 'name') &&
                            _.has(response.responseJSON.name, 'length') &&
                            response.responseJSON.name.length > 0
                            ) {
                            message = response.responseJSON.name[0];
                    }
                    app.showAlert('Failed to execute Test Case', message, 'error');
                }
            });
            } else {
                if (typeof DEBUG != 'undefined' && DEBUG) console.log("Did not pass clientside validation");
            }
        }

    });

return ViewTestRunView;

});<commit_msg>Use right collection variable<commit_after>define([
    'jquery',
    'underscore',
    'backbone',
    'app',
    'simplemde',
    'views/navigationtree/NavigationTreeView',
    'views/projects/ViewNodeItemView',
    'views/testsuites/TestSuiteView',
    'views/testcases/TestCaseView',
    'models/project/ProjectModel',
    'models/testsuite/TestSuiteModel',
    'models/testcase/TestCaseModel',
    'models/testplan/TestPlanModel',
    'models/testrun/TestRunModel',
    'collections/core/ExecutionStatusesCollection',
    'collections/navigationtree/NavigationTreeCollection',
    'collections/testruns/TestRunsCollection',
    'text!templates/testruns/viewTestRunTemplate.html',
    'text!templates/projects/projectNodeItemTemplate.html',
    'text!templates/testsuites/testSuiteNodeItemTemplate.html',
    'text!templates/testcases/testCaseExecuteNodeItemTemplate.html'
    ], function(
        $,
        _,
        Backbone,
        app,
        SimpleMDE,
        NavigationTreeView,
        ViewNodeItemView,
        TestSuiteView,
        TestCaseView,
        ProjectModel,
        TestSuiteModel,
        TestCaseModel,
        TestPlanModel,
        TestRunModel,
        ExecutionStatusesCollection,
        NavigationTreeCollection,
        TestRunsCollection,
        viewTestRunTemplate,
        projectNodeItemTemplate,
        testSuiteNodeItemTemplate,
        testCaseExecuteNodeItemTemplate) {

    /**
     * Displays the navigation tree.
     */
     var ViewTestRunView = Backbone.View.extend({
        el: $("#page"),

        events: {
            'click #save-testplan-btn': 'addTestCasesToTestPlan',
            'click #cancel-testplan-btn': 'cancelAndGoBack',
            'click #execution-history-btn': 'toggleExecutionHistory',
            'click #execute-testcase-btn': 'executeTestCase'
        },

        initialize: function(options) {
            _.bindAll(this,
                'render', 'render2',
                'setProjectId',
                'setTestSuiteId',
                'setTestCaseId',
                'setTestPlanId',
                'setTestRunId',
                'updateNavigationTree',
                'displayLoading',
                'displayProject',
                'displayTestSuite',
                'displayTestCase',
                'cancelAndGoBack',
                'toggleExecutionHistory',
                'executeTestCase');

            var self = this;

            this.projectId = parseInt(options.projectId);
            this.testSuiteId = 0;
            this.testCaseId = 0;
            this.testPlanId = parseInt(options.testPlanId);
            this.testRunId = parseInt(options.testRunId);

            // Collections
            this.navigationTreeCollection = new NavigationTreeCollection({
                projectId: self.projectId
            });
            this.testRunsCollection = new TestRunsCollection({ test_plan_id: this.testPlanId });
            this.executionStatusCollection = new ExecutionStatusesCollection();

            // Models
            this.testPlanModel = new TestPlanModel();
            this.testPlanModel.set('id', this.testPlanId);
            this.testRunModel = new TestRunModel({ test_plan_id: this.testPlanId });
            this.testRunModel.set('id', this.testPlanId);

            // Views
            this.navigationTreeView = new NavigationTreeView({
                draggable: false,
                checkboxes: false,
                rootNodeUrl: '#/testplans/' + options.testPlanId + '/testruns/' + options.testRunId + '/execute',
                nodeUrlPrefix: '#/testplans/' + options.testPlanId + '/testruns/' + options.testRunId,
                nodeUrlSuffix: '/execute',
                collection: self.navigationTreeCollection
            });
            this.viewNodeItemView = new ViewNodeItemView();
            this.testSuiteView = new TestSuiteView();
            this.testCaseView = new TestCaseView();
            this.testCaseSimplemde = null;

            // Events
            Backbone.on('nestor:navigationtree:project_changed', this.updateNavigationTree);
            Backbone.on('nestor:navigationtree_changed', this.updateNavigationTree);

            // For GC
            this.subviews = new Object();
            this.subviews.navigationTreeView = this.navigationTreeView;
            this.subviews.viewNodeItemView = this.viewNodeItemView;
            this.subviews.testSuiteView = this.testSuiteView;
            this.subviews.testCaseView = this.testCaseView;
        },

        cancelAndGoBack: function(evt) {
            Backbone.history.navigate("#/execution", { trigger: false });
        },

        render: function() {
            var self = this;
            $.when(this.testRunModel.fetch(), this.navigationTreeCollection.fetch({ reset: true }))
            .done(function() {
                self.render2();
            })
            ;
        },

        render2: function() {
            $('.item').removeClass('active');
            $('.item a[href="#/execution"]').parent().addClass('active');

            var compiledTemplate = _.template(viewTestRunTemplate, {});
            this.$el.html(compiledTemplate);

            this.$('#content-main').empty();
            this.updateNavigationTree();
            this.delegateEvents();
        },

        updateNavigationTree: function(event) {
            var self = this;
            if (app.currentView == this) {
                console.log('Rendering navigation tree!');
                this.$('#navigation-tree').fancytree({});
                self.navigationTreeView.render({
                    el: self.$('#navigation-tree'),
                    selected: self.testPlanModel.get('test_cases')
                });
            }
        },

        setProjectId: function(id) {
            var projectId = parseInt(id);
            // update project ID in models
            this.projectModel = new ProjectModel();
            this.projectModel.set('id', projectId);

            this.testSuiteModel = new TestSuiteModel();
            this.testSuiteModel.set('project_id', projectId);

            this.testCaseModel = new TestCaseModel();
            this.testCaseModel.set('project_id', projectId);

            this.navigationTreeView.projectId = projectId;

            if (this.projectId !== projectId/* || !$.trim($(this.navigationTreeView.el).html())*/) {
                this.projectId = projectId;
                Backbone.trigger('nestor:navigationtree:project_changed');
                if (app.currentView == this) {
                    this.displayProject();
                }
            }
        },

        setTestSuiteId: function(testSuiteId) {
            // update test suite ID in models
            this.testSuiteModel = new TestSuiteModel();
            this.testSuiteModel.project_id = this.projectId;
            this.testSuiteModel.id = testSuiteId;
            this.testSuiteId = testSuiteId;

            this.testCaseModel = new TestCaseModel();
            this.testCaseModel.project_id = this.projectId;
            this.testCaseModel.testsuite_id = testSuiteId;
        },

        setTestCaseId: function(testCaseId) {
            // update test suite ID in models
            this.testCaseModel = new TestCaseModel();
            this.testCaseModel.project_id = this.projectId;
            this.testCaseModel.testsuite_id = this.testSuiteId;
            this.testCaseModel.id = testCaseId;

            this.testCaseId = testCaseId;
        },

        setTestPlanId: function(testPlanId) {
            this.testPlanId = testPlanId;
            this.testPlanModel.set('id', this.testPlanId);
        },

        setTestRunId: function(testRunId) {
            this.testRunId = testRunId;
            this.testRunModel.set('id', this.testRunId);
        },

        displayLoading: function() {
            this.$('#content-main').empty();
            this.$('#content-main').html('

');
        },

        toggleExecutionHistory: function() {
            this.$("$execution-history-panel").toggle();
        },

        /**
         * Display project node item on the right panel of the screen.
         */
         displayProject: function() {
            this.displayLoading();
            $('.item').removeClass('active');
            $('.item a[href="#/execution"]').parent().addClass('active');
            var self = this;
            this.projectModel.fetch({
                success: function(responseData) {
                    var project = self.projectModel;
                    var data = {
                        project: project,
                        _: _,
                        editable: false
                    };

                    var compiledTemplate = _.template(projectNodeItemTemplate, data);
                    self.viewNodeItemView.$el.html(compiledTemplate);
                    this.$('#content-main').unbind();
                    this.$('#content-main').empty();
                    this.$('#content-main').append(self.viewNodeItemView.el);
                    self.viewNodeItemView.delegateEvents();
                    var tree = this.$('#navigation-tree').fancytree('getTree');
                    if (typeof tree !== typeof undefined && typeof tree.getNodeByKey !== typeof undefined) {
                        var node = tree.getNodeByKey("1-" + project.get('id'));
                        if (node && typeof node !== typeof undefined && typeof node.setActive !== typeof undefined) {
                            node.setActive();
                        } else {
                            console.log('Invalid tree node for node ' + project.get('id'));
                        }
                    } else {
                        console.log('Invalid navigation tree for project ' + self.projectId);
                    }
                },
                error: function() {
                    throw new Error("Failed to fetch project");
                }
            });
        },

        /**
         * Display test suite node item on the right panel of the screen.
         */
         displayTestSuite: function() {
            var self = this;
            this.testSuiteModel.set('id', this.testSuiteId);
            this.testSuiteModel.fetch({
                success: function(responseData) {
                    var data = {
                        testsuite: self.testSuiteModel,
                        _: _,
                        editable: false
                    };

                    var compiledTemplate = _.template(testSuiteNodeItemTemplate, data);
                    self.viewNodeItemView.$el.html(compiledTemplate);
                    self.$('#content-main').empty();
                    self.$('#content-main').append(self.viewNodeItemView.el);
                },
                error: function() {
                    throw new Error("Failed to fetch test suite");
                }
            });
        },

        /**
         * Display test case node item on the right panel of the screen.
         */
         displayTestCase: function() {
            var self = this;
            this.testCaseModel.set('project_id', this.projectId);
            this.testCaseModel.set('test_suite_id', this.testSuiteId);
            this.testCaseModel.set('id', this.testCaseId);
            this.testCaseModel.url = '/api/testplans/' + this.testPlanId + '/testruns/' + this.testRunId + '/testsuites/' + this.testSuiteId + '/testcases/' + this.testCaseId + '/executions';
            var self = this;
            $.when(this.testCaseModel.fetch(), this.executionStatusCollection.fetch())
                .done(function(testCaseResponse, executionStatusesResponse) {
                    var testcase = self.testCaseModel;
                    var execution_statuses = self.executionStatusCollection.models;
                    var data = {
                        testcase: testcase,
                        execution_statuses: execution_statuses,
                        _: _
                    };

                    var compiledTemplate = _.template(testCaseExecuteNodeItemTemplate, data);
                    self.viewNodeItemView.$el.html(compiledTemplate);

                    self.$('#content-main').empty();
                    self.$('#content-main').append(self.viewNodeItemView.el);
                    self.$('#navigation-tree').fancytree('getTree').getNodeByKey("3-" + testcase.get('id')).setActive();

                    self.testCaseSimplemde = new SimpleMDE({
                        autoDownloadFontAwesome: true,
                        autofocus: false,
                        autosave: {
                            enabled: false
                        },
                        element: $('#testcase-notes-input')[0],
                        indentWithTabs: false,
                        spellChecker: false,
                        tabSize: 4
                    });
                })
            ;
        },

        /**
         * Execute test case.
         */
        executeTestCase: function() {
            var self = this;
            if (this.$("#execute-testcase-form").parsley().validate()) {
                var testRun = this.testRunsCollection.create({
                    notes: this.testCaseSimplemde.value(),
                    execution_statuses_id: this.$("#testcase-executionstatus_id-input").val()
                }, {
                    wait: true,
                    success: function(mod, res) {
                        app.showAlert('Success!', 'Test case ' + this.$("#testrun-name-input").val() + ' execution status updated!', 'success')
                        Backbone.history.navigate("#/testplans/" + self.testPlanId + '/testruns' + self.testRunId + '/testsuites/' + self.testSuiteId + '/testcases/' + self.testCaseId + '/execute', {
                            trigger: false
                        });
                    },
                    error: function(model, response, options) {
                        var message = _.has(response, 'statusText') ? response.statusText : 'Unknown error!';
                        if (
                            _.has(response, 'responseJSON') &&
                            _.has(response.responseJSON, 'name') &&
                            _.has(response.responseJSON.name, 'length') &&
                            response.responseJSON.name.length > 0
                            ) {
                            message = response.responseJSON.name[0];
                    }
                    app.showAlert('Failed to execute Test Case', message, 'error');
                }
            });
            } else {
                if (typeof DEBUG != 'undefined' && DEBUG) console.log("Did not pass clientside validation");
            }
        }

    });

return ViewTestRunView;

});<|endoftext|>


<commit_before>// @flow
import Icon from './icon'
import React, {PureComponent} from 'react'
import {globalColors} from '../styles'
import {ClickableBox, NativeImage, Box} from './index.native'

import type {AvatarSize} from './avatar'
import type {IconType} from './icon'

type ImageProps = {
  onLoadEnd: () => void,
  opacity: ?number,
  size: AvatarSize,
  url: string,
}

type Props = {
  borderColor: ?string,
  children: any,
  followIconStyle: ?Object,
  followIconType: ?IconType,
  followIconSize: number,
  loadingColor: ?string,
  onClick?: ?(() => void),
  opacity: ?number,
  size: AvatarSize,
  style?: ?Object,
  url: ?string,
}

type State = {
  loaded: boolean,
}

// Android doesn't handle background colors border radius setting
const backgroundOffset = 1
const Background = ({loaded, loadingColor, size}) => (
  <Box
    style={{
      backgroundColor: loaded ? globalColors.white : loadingColor || globalColors.lightGrey,
      borderRadius: size / 2,
      bottom: backgroundOffset,
      left: backgroundOffset,
      position: 'absolute',
      right: backgroundOffset,
      top: backgroundOffset,
    }} />
)

class UserImage extends PureComponent<void, ImageProps, void> {
  render () {
    const {url, size, onLoadEnd, opacity = 1} = this.props

    return (
      <NativeImage
        source={url}
        onLoadEnd={onLoadEnd}
        style={{
          borderRadius: size / 2,
          bottom: 0,
          height: size,
          left: 0,
          opacity,
          position: 'absolute',
          right: 0,
          top: 0,
          width: size,
        }}
      />
    )
  }
}

const borderOffset = -1
const borderSize = 2
// Layer on top to extend outside of the image
const Border = ({borderColor, size}) => (
  <Box
    style={{
      borderColor,
      borderRadius: size / 2,
      borderWidth: borderSize,
      bottom: borderOffset,
      left: borderOffset,
      position: 'absolute',
      right: borderOffset,
      top: borderOffset,
    }}
  />
)

class AvatarRender extends PureComponent<void, Props, State> {
  state: State = {
    loaded: false,
  }

  _mounted: boolean = false

  _onLoadOrError = () => {
    if (this._mounted) {
      this.setState({loaded: true})
    }
  }

  componentWillReceiveProps (nextProps: Props) {
    if (this.props.url !== nextProps.url) {
      this.setState({loaded: false})
    }
  }

  componentDidMount () {
    this._mounted = true
  }

  componentWillUnmount () {
    this._mounted = false
  }

  render () {
    const {url, onClick, style, size, loadingColor, borderColor, opacity, followIconType, followIconStyle, followIconSize, children} = this.props

    return (
      <ClickableBox onClick={onClick} feedback={false}>
        <Box style={{
          height: size,
          position: 'relative',
          width: size,
          ...style,
        }}>
          <Background loaded={this.state.loaded} loadingColor={loadingColor} size={size} />
          {!!url && <UserImage
            opacity={opacity}
            onLoadEnd={this._onLoadOrError}
            size={size}
            url={url}
          /> }
          {!!borderColor && <Border borderColor={borderColor} size={size} />}
          {followIconType && <Icon type={followIconType} style={{...followIconStyle, width: followIconSize, height: followIconSize}} />}
          {children}
        </Box>
      </ClickableBox>
    )
  }
}

export default AvatarRender
<commit_msg>Fix avatar border clipping on android (#6791)<commit_after>// @flow
import Icon from './icon'
import React, {PureComponent} from 'react'
import {globalColors} from '../styles'
import {ClickableBox, NativeImage, Box} from './index.native'

import type {AvatarSize} from './avatar'
import type {IconType} from './icon'

type ImageProps = {
  onLoadEnd: () => void,
  opacity: ?number,
  size: AvatarSize,
  url: string,
}

type Props = {
  borderColor: ?string,
  children: any,
  followIconStyle: ?Object,
  followIconType: ?IconType,
  followIconSize: number,
  loadingColor: ?string,
  onClick?: ?(() => void),
  opacity: ?number,
  size: AvatarSize,
  style?: ?Object,
  url: ?string,
}

type State = {
  loaded: boolean,
}

// Android doesn't handle background colors border radius setting
const backgroundOffset = 1
const Background = ({loaded, loadingColor, size}) => (
  <Box
    style={{
      backgroundColor: loaded ? globalColors.white : loadingColor || globalColors.lightGrey,
      borderRadius: size / 2,
      bottom: backgroundOffset,
      left: backgroundOffset,
      position: 'absolute',
      right: backgroundOffset,
      top: backgroundOffset,
    }} />
)

class UserImage extends PureComponent<void, ImageProps, void> {
  render () {
    const {url, size, onLoadEnd, opacity = 1} = this.props

    return (
      <NativeImage
        source={url}
        onLoadEnd={onLoadEnd}
        style={{
          borderRadius: size / 2,
          bottom: 0,
          height: size,
          left: 0,
          opacity,
          position: 'absolute',
          right: 0,
          top: 0,
          width: size,
        }}
      />
    )
  }
}

const borderOffset = -1
const borderSize = 2
// Layer on top to extend outside of the image
const Border = ({borderColor, size}) => (
  <Box
    style={{
      borderColor,
      borderRadius: size / 2,
      borderWidth: borderSize,
      bottom: borderOffset,
      left: borderOffset,
      position: 'absolute',
      right: borderOffset,
      top: borderOffset,
      margin: borderSize / 2,
    }}
  />
)

class AvatarRender extends PureComponent<void, Props, State> {
  state: State = {
    loaded: false,
  }

  _mounted: boolean = false

  _onLoadOrError = () => {
    if (this._mounted) {
      this.setState({loaded: true})
    }
  }

  componentWillReceiveProps (nextProps: Props) {
    if (this.props.url !== nextProps.url) {
      this.setState({loaded: false})
    }
  }

  componentDidMount () {
    this._mounted = true
  }

  componentWillUnmount () {
    this._mounted = false
  }

  render () {
    const {url, onClick, style, size, loadingColor, borderColor, opacity, followIconType, followIconStyle, followIconSize, children} = this.props

    return (
      <ClickableBox onClick={onClick} feedback={false}>
        <Box style={{
          height: size,
          position: 'relative',
          width: size,
          ...style,
        }}>
          <Background loaded={this.state.loaded} loadingColor={loadingColor} size={size} />
          {!!url && <UserImage
            opacity={opacity}
            onLoadEnd={this._onLoadOrError}
            size={size}
            url={url}
          /> }
          {!!borderColor && <Border borderColor={borderColor} size={size} />}
          {followIconType && <Icon type={followIconType} style={{...followIconStyle, width: followIconSize, height: followIconSize}} />}
          {children}
        </Box>
      </ClickableBox>
    )
  }
}

export default AvatarRender
<|endoftext|>


<commit_before>/*!
 * Copyright 2014 Digital Services, University of Cambridge Licensed
 * under the Educational Community 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://opensource.org/licenses/ECL-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.
 */

define(['lodash', 'moment', 'gh.api.util', 'gh.api.config'], function(_, moment, utilAPI, configAPI) {

    ///////////////
    // RENDERING //
    ///////////////

    /**
     * Render the batch edit date container
     *
     * @param  {Number}      maxNumberOfWeeks    The maximum number of weeks in a term associated to the app
     * @param  {Object[]}    weeksInUse          Array containing the numbers of the weeks that are selected
     * @param  {Object[]}    termsInUse          The terms of the events that were selected
     * @param  {Object}      daysInUse           Object describing the days in use by the selection and their start and end times
     * @private
     */
    var renderBatchDate = function(maxNumberOfWeeks, weeksInUse, termsInUse, daysInUse) {
        utilAPI.renderTemplate($('#gh-batch-edit-date-template'), {
            'gh': require('gh.core'),
            'numberOfWeeks': maxNumberOfWeeks,
            'weeksInUse': weeksInUse,
            'termsInUse': termsInUse,
            'daysInUse': daysInUse
        }, $('#gh-batch-edit-date-container'));
    };

    /**
     * Build the data object required to render the date batch edit template
     *
     * @private
     */
    var buildBatchDateObject = function() {
        // Get the checked events from the batch edit container
        var $rows = $('.gh-batch-edit-events-container tr.info:visible');
        // Get the maximum number of weeks in a term
        var maxNumberOfWeeks = getMaxNumberOfWeeks();
        // Get the weeks that are in use by the selection
        var weeksInUse = getWeeksInUse($rows);
        // Get the term data for the selection
        var termsInUse = getTermsInUse($rows);
        // Get the unique days in the week to render time pickers for
        var daysInUse = getDaysInUse($rows);
        // Render the batch date editor
        renderBatchDate(maxNumberOfWeeks, weeksInUse, termsInUse, daysInUse);
    };

    ///////////////
    // UTILITIES //
    ///////////////

    /**
     * Remove events from a specified week number
     *
     * @param  {Number}    weekNumber    The week number to delete events from
     * @private
     */
    var removeEventsInWeek = function(weekNumber) {
        // Get the checked events from the batch edit container
        var $rows = $('.gh-batch-edit-events-container tr.info:visible');
        // For each row, check if the event is taking place in the week that is to be removed
        _.each($rows, function($row) {
            $row = $($row);
            // Get the start date of the event
            var startDate = $row.find('.gh-event-date').attr('data-start');
            // Get the week in which the event takes place
            var dateWeek = utilAPI.getWeekInTerm(startDate);
            // If the event takes place in the week that needs to be removed, delete it
            if (dateWeek === weekNumber) {
                $row.find('.gh-event-delete').click();
            }
        });
    };

    /**
     * Add another day to the terms based on the selection of weeks
     *
     * @param {Number}    [weekNumber]    The optional number of the week to add the events to
     *
     * @private
     */
    var addAnotherDay = function(weekNumber) {
        // Default the container to look for selected weeks in
        var $weeks = $('#gh-batch-edit-date-picker input:checked');
        // If a weeknumber is defined, only add events to that week
        if (weekNumber) {
            $weeks = $('#gh-batch-edit-date-picker input[value="' + weekNumber + '"]');
        }

        // For each term selected, add events
        _.each($('#gh-batch-edit-date-picker-container').data('terms').split(','), function(termName) {
            // For each selected week, add events
            _.each($weeks, function(chk) {
                // For each row of days, add the event
                _.each($('.gh-batch-edit-time-picker'), function($timePickerContainer) {
                    // Get the week number
                    var eventWeek = parseInt(chk.value, 10);
                    // Get the day number
                    var eventDay = parseInt($('#gh-batch-edit-day-picker', $timePickerContainer).val(), 10);
                    // Get the date by week and day
                    var dateByWeekAndDay = utilAPI.getDateByWeekAndDay(termName, eventWeek, eventDay);

                    var eventYear = dateByWeekAndDay.getFullYear();
                    var eventMonth = dateByWeekAndDay.getMonth();
                    eventDay = dateByWeekAndDay.getDate();
                    var eventStartHour = parseInt($('#gh-batch-edit-hours-start', $timePickerContainer).val(), 10);
                    var eventStartMinute = parseInt($('#gh-batch-edit-minutes-start', $timePickerContainer).val(), 10);
                    var eventEndHour = parseInt($('#gh-batch-edit-hours-end', $timePickerContainer).val(), 10);
                    var eventEndMinute = parseInt($('#gh-batch-edit-minutes-end', $timePickerContainer).val(), 10);

                    // Create the start date of the event
                    var startDate = moment([eventYear, eventMonth, eventDay, eventStartHour, eventStartMinute, 0, 0]);
                    // Create the end date of the event
                    var endDate = moment([eventYear, eventMonth, eventDay, eventEndHour, eventEndMinute, 0, 0]);

                    // Send off an event that will be picked up by the batch edit and add the rows to the terms
                    $(document).trigger('gh.batchedit.addevent', {
                        'eventContainer': $('.gh-batch-edit-events-container[data-term="' + termName + '"]').find('tbody'),
                        'eventObj': {
                            'tempId': utilAPI.generateRandomString(), // The actual ID hasn't been generated yet
                            'isNew': true, // Used in the template to know this one needs special handling
                            'selected': true,
                            'displayName': $('.gh-jeditable-series-title').text(),
                            'end': moment(endDate).utc().format(),
                            'location': '',
                            'notes': 'Lecture',
                            'organisers': null,
                            'start': moment(startDate).utc().format()
                        }
                    });
                });
            });
        });
    };

    /**
     * Get the days in use by the selection
     *
     * @param  {Object[]}    $rows    Array of rows that are selected for batch edit
     * @param  {Object}               Object describing the days in use by the selection and their start and end times
     * @private
     */
    var getDaysInUse = function($rows) {
        var daysObj = {};
        _.each($rows, function($row) {
            $row = $($row);
            var startDate = new Date($row.find('.gh-event-date').attr('data-start'));
            var endDate = new Date($row.find('.gh-event-date').attr('data-end'));
            var dayOfTheWeek = new Date(startDate).getDay();
            daysObj[dayOfTheWeek] = {
                'startHour': startDate.getHours(),
                'endHour': endDate.getHours(),
                'startMinute': startDate.getMinutes(),
                'endMinute': endDate.getMinutes()
            };
        });

        return daysObj;
    };

    /**
     * Get the terms that are in use by the checked event rows
     *
     * @param  {Object[]}    $rows    Array of rows that are selected for batch edit
     * @return {Object[]}             Array of terms used by the checked event rows
     * @private
     */
    var getTermsInUse = function($rows) {
        var appTerms = configAPI.getAppTerm();
        var termsInUse = [];
        _.each(appTerms, function(term) {
            _.each($rows, function(row) {
                var termStart = new Date(term.start);
                var termEnd = new Date(term.end);
                var eventStart = new Date($(row).find('.gh-event-date').attr('data-start'));

                if (eventStart > termStart && eventStart < termEnd) {
                    termsInUse.push(term);
                }
            });

            // If the `select all` checkbox is checked, include this term
            if ($('.gh-batch-edit-events-container[data-term="' + term.name + '"] .gh-select-all').is(':checked')) {
                termsInUse.push(term);
            }
        });

        return _.uniq(termsInUse);
    };

    /**
     * Get the week numbers that are in use by the checked event rows
     *
     * @param  {Object[]}    $rows    Array of rows that are selected for batch edit
     * @return {Number[]}             Array week numbers that are in use by the checked event rows
     * @private
     */
    var getWeeksInUse = function($rows) {
        // Keep track of the weeks in use
        var weeksInUse = [];
        // Extract the weeks from the batch
        _.each($rows, function(row) {
            var start = $(row).find('.gh-event-date').attr('data-start');
            weeksInUse.push(utilAPI.getWeekInTerm(start));
        });
        return _.uniq(weeksInUse);
    };

    /**
     * Get the maximum number of weeks in an academic year term and return it
     *
     * @return {Object}    The maximum number of weeks in an academic year term
     * @private
     */
    var getMaxNumberOfWeeks = function() {
        // Get the configuration
        var config = require('gh.core').config;
        // Get the correct terms associated to the current application
        var terms = config.terms[config.academicYear];
        // Cache the max weeks in a term
        var maxWeeks = 0;
        // Loop over the terms and cache the highest number of weeks
        _.each(terms, function(term) {
            var weeksInTerm = utilAPI.getWeeksInTerm(term);
            if (weeksInTerm > maxWeeks) {
                maxWeeks = weeksInTerm;
            }
        });
        // Return the maximum number of weeks in the term
        return maxWeeks;
    };

    /**
     * Batch edit the time on which events start and finish
     *
     * @private
     */
    var batchEditTime = function() {
        // Get the container in which the edits where made
        var $timeContainer = $($(this).closest('.gh-batch-edit-time-picker'));
        // Get the values that changed
        var prevEventDay = parseInt($('#gh-batch-edit-day-picker', $timeContainer).attr('data-prev'), 10);
        var eventDay = parseInt($('#gh-batch-edit-day-picker', $timeContainer).val(), 10);
        var eventStartHour = parseInt($('#gh-batch-edit-hours-start', $timeContainer).val(), 10);
        var eventStartMinute = parseInt($('#gh-batch-edit-minutes-start', $timeContainer).val(), 10);
        var eventEndHour = parseInt($('#gh-batch-edit-hours-end', $timeContainer).val(), 10);
        var eventEndMinute = parseInt($('#gh-batch-edit-minutes-end', $timeContainer).val(), 10);

        // Loop over the selected events and change the ones that match the previous eventDay
        // value (assuming it was changed)
        var $rows = $('.gh-batch-edit-events-container tr.info:visible');
        _.each($rows, function($row) {
            $row = $($row);
            // Get the date the event starts on
            var eventStart = new Date($row.find('.gh-event-date').attr('data-start'));

            // Only update the date when the event takes place on the day that was selected in the picker
            var dayNumberToEdit = prevEventDay || eventDay;
            if (eventStart.getDay() === dayNumberToEdit) {
                // Get the date the event finishes on
                var eventEnd = new Date($row.find('.gh-event-date').attr('data-end'));
                // Get which week of the term this event takes place in
                var weekInTerm = utilAPI.getWeekInTerm(eventStart);
                // Get the name of the term this date is in
                var termName = $row.closest('.gh-batch-edit-events-container').attr('data-term');
                // Get the date the event would be on after the change
                var newDate = utilAPI.getDateByWeekAndDay(termName, weekInTerm, eventDay);

                // Create the new date for the row
                eventStart = moment([newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), eventStartHour, eventStartMinute, 0, 0]).utc().format();
                eventEnd = moment([newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), eventEndHour, eventEndMinute, 0, 0]).utc().format();

                // Re-render the date fields
                var content = utilAPI.renderTemplate($('#gh-edit-date-field-template'), {
                    'data': {
                        'start': eventStart,
                        'end': eventEnd
                    },
                    'utilAPI': utilAPI
                });

                // Update the trigger
                $row.find('.gh-event-date').attr('data-start', eventStart).attr('data-end', eventEnd).html(content);

                // Trigger a change of the datepicker
                $(document).trigger('gh.datepicker.change', $row.find('.gh-event-date'));
            }
        });
    };

    /**
     * Add a helper class to the checked checkbox container to visually indicate selection
     *
     * @private
     */
    var batchEditDateWeeks = function() {
        // If the input field is checked, add the appropriate class to its parent
        if ($(this).is(':checked')) {
            // Add the class
            $(this).closest('.checkbox').addClass('gh-batch-edit-date-picker-selected');
            // Add all events to the associated week
            addAnotherDay(parseInt($(this).val(), 10));
        } else {
            // Remove the class
            $(this).closest('.checkbox').removeClass('gh-batch-edit-date-picker-selected');
            // Remove all events associated to the week
            removeEventsInWeek(parseInt($(this).val(), 10));
        }
        // Get the weeks that are in use by the selection
        var weeksInUse = $('#gh-batch-edit-date-picker input:checked').length;
        // Update the weeks in use label
        weeksInUse = weeksInUse + (weeksInUse === 1 ? ' week' : ' weeks') + ' selected';
        $('#gh-batch-edit-date-picker-container > small').text(weeksInUse);
    };

    /**
     * Remove a helper class from all checkbox containers that visually indicate focus
     *
     * @private
     */
    var blurEditDateWeeks = function() {
        $('#gh-batch-edit-date-picker .checkbox').removeClass('gh-focus');
    };

    /**
     * Add a helper class to the focused checkbox containers to visually indicate focus
     *
     * @private
     */
    var focusEditDateWeeks = function() {
        $(this).closest('.checkbox').addClass('gh-focus');
    };

    ////////////////////
    // INITIALISATION //
    ////////////////////

    /**
     * Add handlers to various elements in batch date edit
     *
     * @private
     */
    var addBinding = function() {
        // Initialisation
        $(document).on('gh.batchdate.setup', buildBatchDateObject);
        $(document).on('gh.datepicker.change', buildBatchDateObject);

        // Used to throttle function calls
        var delayed = null;
        $('body').on('change', '.gh-select-single', function() {
            // Clear the timeout to reset the timer
            clearTimeout(delayed);
            // Set a delay on the function call to allow combining of multiple events
            delayed = setTimeout(function () {
                // The timeout passed and the function gets called
                buildBatchDateObject();
            }, 1);
        });

        $('body').on('change', '.gh-select-all', function() {
            if ($('.gh-select-single:checked').length) {
                buildBatchDateObject();
            }
        });

        // Week checkbox related events
        $('body').on('change', '#gh-batch-edit-date-picker-container input', batchEditDateWeeks);
        $('body').on('focus', '#gh-batch-edit-date-picker-container input', focusEditDateWeeks);
        $('body').on('blur', '#gh-batch-edit-date-picker-container input', blurEditDateWeeks);

        // Time selectors
        $('body').on('focus click', '#gh-batch-edit-day-picker', function() {
            $(this).attr('data-prev', $(this).val());
        });
        $('body').on('change', '#gh-batch-edit-day-picker', batchEditTime);
        $('body').on('change', '#gh-batch-edit-hours-start', batchEditTime);
        $('body').on('change', '#gh-batch-edit-hours-end', batchEditTime);
        $('body').on('change', '#gh-batch-edit-minutes-start', batchEditTime);
        $('body').on('change', '#gh-batch-edit-minutes-end', batchEditTime);

        // Adding a new day
        $('body').on('click', '.gh-batch-edit-date-add-day', function() {
            // This function takes arguments but we don't need to supply any here, so we
            // call it inside of a function wrapper
            addAnotherDay();
        });
    };

    addBinding();
});
<commit_msg>Finish batch header refinements<commit_after>/*!
 * Copyright 2014 Digital Services, University of Cambridge Licensed
 * under the Educational Community 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://opensource.org/licenses/ECL-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.
 */

define(['lodash', 'moment', 'gh.api.util', 'gh.api.config'], function(_, moment, utilAPI, configAPI) {

    ///////////////
    // RENDERING //
    ///////////////

    /**
     * Render the batch edit date container
     *
     * @param  {Number}      maxNumberOfWeeks    The maximum number of weeks in a term associated to the app
     * @param  {Object[]}    weeksInUse          Array containing the numbers of the weeks that are selected
     * @param  {Object[]}    termsInUse          The terms of the events that were selected
     * @param  {Object}      daysInUse           Object describing the days in use by the selection and their start and end times
     * @private
     */
    var renderBatchDate = function(maxNumberOfWeeks, weeksInUse, termsInUse, daysInUse) {
        utilAPI.renderTemplate($('#gh-batch-edit-date-template'), {
            'gh': require('gh.core'),
            'numberOfWeeks': maxNumberOfWeeks,
            'weeksInUse': weeksInUse,
            'termsInUse': termsInUse,
            'daysInUse': daysInUse
        }, $('#gh-batch-edit-date-container'));
    };

    /**
     * Build the data object required to render the date batch edit template
     *
     * @private
     */
    var buildBatchDateObject = function() {
        // Get the checked events from the batch edit container
        var $rows = $('.gh-batch-edit-events-container tr.info:visible');
        // Get the maximum number of weeks in a term
        var maxNumberOfWeeks = getMaxNumberOfWeeks();
        // Get the weeks that are in use by the selection
        var weeksInUse = getWeeksInUse($rows);
        // Get the term data for the selection
        var termsInUse = getTermsInUse($rows);
        // Get the unique days in the week to render time pickers for
        var daysInUse = getDaysInUse($rows);
        // Render the batch date editor
        renderBatchDate(maxNumberOfWeeks, weeksInUse, termsInUse, daysInUse);
    };

    ///////////////
    // UTILITIES //
    ///////////////

    /**
     * Remove events from a specified week number
     *
     * @param  {Number}    weekNumber    The week number to delete events from
     * @private
     */
    var removeEventsInWeek = function(weekNumber) {
        // Get the checked events from the batch edit container
        var $rows = $('.gh-batch-edit-events-container tr.info:visible');
        // For each row, check if the event is taking place in the week that is to be removed
        _.each($rows, function($row) {
            $row = $($row);
            // Get the start date of the event
            var startDate = $row.find('.gh-event-date').attr('data-start');
            // Get the week in which the event takes place
            var dateWeek = utilAPI.getWeekInTerm(startDate);
            // If the event takes place in the week that needs to be removed, delete it
            if (dateWeek === weekNumber) {
                $row.find('.gh-event-delete').click();
            }
        });
    };

    /**
     * Add another day to the terms based on the selection of weeks
     *
     * @param {Number}    [weekNumber]    The optional number of the week to add the events to
     * @private
     */
    var addAnotherDay = function() {
        // Default the container to look for selected weeks in
        var $weeks = $('#gh-batch-edit-date-picker input:checked');

        // For each term selected, add events
        _.each($('#gh-batch-edit-date-picker-container').data('terms').split(','), function(termName) {
            // For each selected week, add events
            _.each($weeks, function(chk) {
                // For each row of days, add the event
                _.each($('.gh-batch-edit-time-picker'), function($timePickerContainer) {
                    // Get the week number
                    var eventWeek = parseInt(chk.value, 10);
                    // Get the day number
                    var eventDay = parseInt($('#gh-batch-edit-day-picker', $timePickerContainer).val(), 10);
                    // Get the date by week and day
                    var dateByWeekAndDay = utilAPI.getDateByWeekAndDay(termName, eventWeek, eventDay);

                    var eventYear = dateByWeekAndDay.getFullYear();
                    var eventMonth = dateByWeekAndDay.getMonth();
                    eventDay = dateByWeekAndDay.getDate();
                    var eventStartHour = parseInt($('#gh-batch-edit-hours-start', $timePickerContainer).val(), 10);
                    var eventStartMinute = parseInt($('#gh-batch-edit-minutes-start', $timePickerContainer).val(), 10);
                    var eventEndHour = parseInt($('#gh-batch-edit-hours-end', $timePickerContainer).val(), 10);
                    var eventEndMinute = parseInt($('#gh-batch-edit-minutes-end', $timePickerContainer).val(), 10);

                    // Create the start date of the event
                    var startDate = moment([eventYear, eventMonth, eventDay, eventStartHour, eventStartMinute, 0, 0]);
                    // Create the end date of the event
                    var endDate = moment([eventYear, eventMonth, eventDay, eventEndHour, eventEndMinute, 0, 0]);

                    // Send off an event that will be picked up by the batch edit and add the rows to the terms
                    var alreadyAdded = $('.info .gh-event-date[data-start="' + utilAPI.convertUnixDatetoISODate(startDate.utc().format()) + '"]').length;
                    if (!alreadyAdded) {
                        $(document).trigger('gh.batchedit.addevent', {
                            'eventContainer': $('.gh-batch-edit-events-container[data-term="' + termName + '"]').find('tbody'),
                            'eventObj': {
                                'tempId': utilAPI.generateRandomString(), // The actual ID hasn't been generated yet
                                'isNew': true, // Used in the template to know this one needs special handling
                                'selected': true,
                                'displayName': $('.gh-jeditable-series-title').text(),
                                'end': utilAPI.convertUnixDatetoISODate(moment(endDate).utc().format()),
                                'location': '',
                                'notes': 'Lecture',
                                'organisers': null,
                                'start': utilAPI.convertUnixDatetoISODate(moment(startDate).utc().format())
                            }
                        });
                    }
                });
            });
        });
    };

    /**
     * Get the days in use by the selection
     *
     * @param  {Object[]}    $rows    Array of rows that are selected for batch edit
     * @param  {Object}               Object describing the days in use by the selection and their start and end times
     * @private
     */
    var getDaysInUse = function($rows) {
        var daysObj = {};
        _.each($rows, function($row) {
            $row = $($row);
            var startDate = new Date($row.find('.gh-event-date').attr('data-start'));
            var endDate = new Date($row.find('.gh-event-date').attr('data-end'));
            var dayOfTheWeek = new Date(startDate).getDay();
            daysObj[dayOfTheWeek] = {
                'startHour': startDate.getHours(),
                'endHour': endDate.getHours(),
                'startMinute': startDate.getMinutes(),
                'endMinute': endDate.getMinutes()
            };
        });

        return daysObj;
    };

    /**
     * Get the terms that are in use by the checked event rows
     *
     * @param  {Object[]}    $rows    Array of rows that are selected for batch edit
     * @return {Object[]}             Array of terms used by the checked event rows
     * @private
     */
    var getTermsInUse = function($rows) {
        var appTerms = configAPI.getAppTerm();
        var termsInUse = [];
        _.each(appTerms, function(term) {
            _.each($rows, function(row) {
                var termStart = new Date(term.start);
                var termEnd = new Date(term.end);
                var eventStart = new Date($(row).find('.gh-event-date').attr('data-start'));

                if (eventStart > termStart && eventStart < termEnd) {
                    termsInUse.push(term);
                }
            });

            // If the `select all` checkbox is checked, include this term
            if ($('.gh-batch-edit-events-container[data-term="' + term.name + '"] .gh-select-all').is(':checked')) {
                termsInUse.push(term);
            }
        });

        return _.uniq(termsInUse);
    };

    /**
     * Get the week numbers that are in use by the checked event rows
     *
     * @param  {Object[]}    $rows    Array of rows that are selected for batch edit
     * @return {Number[]}             Array week numbers that are in use by the checked event rows
     * @private
     */
    var getWeeksInUse = function($rows) {
        // Keep track of the weeks in use
        var weeksInUse = [];
        // Extract the weeks from the batch
        _.each($rows, function(row) {
            var start = $(row).find('.gh-event-date').attr('data-start');
            weeksInUse.push(utilAPI.getWeekInTerm(start));
        });
        return _.uniq(weeksInUse);
    };

    /**
     * Get the maximum number of weeks in an academic year term and return it
     *
     * @return {Object}    The maximum number of weeks in an academic year term
     * @private
     */
    var getMaxNumberOfWeeks = function() {
        // Get the configuration
        var config = require('gh.core').config;
        // Get the correct terms associated to the current application
        var terms = config.terms[config.academicYear];
        // Cache the max weeks in a term
        var maxWeeks = 0;
        // Loop over the terms and cache the highest number of weeks
        _.each(terms, function(term) {
            var weeksInTerm = utilAPI.getWeeksInTerm(term);
            if (weeksInTerm > maxWeeks) {
                maxWeeks = weeksInTerm;
            }
        });
        // Return the maximum number of weeks in the term
        return maxWeeks;
    };

    /**
     * Batch edit the time on which events start and finish
     *
     * @private
     */
    var batchEditTime = function() {
        // Get the container in which the edits where made
        var $timeContainer = $($(this).closest('.gh-batch-edit-time-picker'));
        // Get the values that changed
        var prevEventDay = parseInt($('#gh-batch-edit-day-picker', $timeContainer).attr('data-prev'), 10);
        var eventDay = parseInt($('#gh-batch-edit-day-picker', $timeContainer).val(), 10);
        var eventStartHour = parseInt($('#gh-batch-edit-hours-start', $timeContainer).val(), 10);
        var eventStartMinute = parseInt($('#gh-batch-edit-minutes-start', $timeContainer).val(), 10);
        var eventEndHour = parseInt($('#gh-batch-edit-hours-end', $timeContainer).val(), 10);
        var eventEndMinute = parseInt($('#gh-batch-edit-minutes-end', $timeContainer).val(), 10);

        // Loop over the selected events and change the ones that match the previous eventDay
        // value (assuming it was changed)
        var $rows = $('.gh-batch-edit-events-container tr.info:visible');
        _.each($rows, function($row) {
            $row = $($row);
            // Get the date the event starts on
            var eventStart = new Date($row.find('.gh-event-date').attr('data-start'));

            // Only update the date when the event takes place on the day that was selected in the picker
            var dayNumberToEdit = prevEventDay || eventDay;
            if (eventStart.getDay() === dayNumberToEdit) {
                // Get the date the event finishes on
                var eventEnd = new Date($row.find('.gh-event-date').attr('data-end'));
                // Get which week of the term this event takes place in
                var weekInTerm = utilAPI.getWeekInTerm(eventStart);
                // Get the name of the term this date is in
                var termName = $row.closest('.gh-batch-edit-events-container').attr('data-term');
                // Get the date the event would be on after the change
                var newDate = utilAPI.getDateByWeekAndDay(termName, weekInTerm, eventDay);

                // Create the new date for the row
                eventStart = moment([newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), eventStartHour, eventStartMinute, 0, 0]).utc().format();
                eventEnd = moment([newDate.getFullYear(), newDate.getMonth(), newDate.getDate(), eventEndHour, eventEndMinute, 0, 0]).utc().format();

                // Re-render the date fields
                var content = utilAPI.renderTemplate($('#gh-edit-date-field-template'), {
                    'data': {
                        'start': eventStart,
                        'end': eventEnd
                    },
                    'utilAPI': utilAPI
                });

                // Update the trigger
                $row.find('.gh-event-date').attr('data-start', eventStart).attr('data-end', eventEnd).html(content);

                // Trigger a change of the datepicker
                $(document).trigger('gh.datepicker.change', $row.find('.gh-event-date'));
            }
        });
    };

    /**
     * Batch edit weeks by adding and removing events based on the selection
     *
     * @private
     */
    var batchEditDateWeeks = function() {
        // If the input field is checked, add the appropriate class to its parent
        if ($(this).is(':checked')) {
            // Add the class
            $(this).closest('.checkbox').addClass('gh-batch-edit-date-picker-selected');
            // Add all events to the associated week
            addAnotherDay();
        } else {
            // Remove the class
            $(this).closest('.checkbox').removeClass('gh-batch-edit-date-picker-selected');
            // Remove all events associated to the week
            removeEventsInWeek(parseInt($(this).val(), 10));
        }
        // Get the weeks that are in use by the selection
        var weeksInUse = $('#gh-batch-edit-date-picker input:checked').length;
        // Update the weeks in use label
        weeksInUse = weeksInUse + (weeksInUse === 1 ? ' week' : ' weeks') + ' selected';
        $('#gh-batch-edit-date-picker-container > small').text(weeksInUse);
    };

    /**
     * Remove a helper class from all checkbox containers that visually indicate focus
     *
     * @private
     */
    var blurEditDateWeeks = function() {
        $('#gh-batch-edit-date-picker .checkbox').removeClass('gh-focus');
    };

    /**
     * Add a helper class to the focused checkbox containers to visually indicate focus
     *
     * @private
     */
    var focusEditDateWeeks = function() {
        $(this).closest('.checkbox').addClass('gh-focus');
    };

    ////////////////////
    // INITIALISATION //
    ////////////////////

    /**
     * Add handlers to various elements in batch date edit
     *
     * @private
     */
    var addBinding = function() {
        // Initialisation
        $(document).on('gh.batchdate.setup', buildBatchDateObject);
        $(document).on('gh.datepicker.change', buildBatchDateObject);

        // Used to throttle function calls
        var delayed = null;
        $('body').on('change', '.gh-select-single', function() {
            // Clear the timeout to reset the timer
            clearTimeout(delayed);
            // Set a delay on the function call to allow combining of multiple events
            delayed = setTimeout(function () {
                // The timeout passed and the function gets called
                buildBatchDateObject();
            }, 1);
        });

        $('body').on('change', '.gh-select-all', function() {
            if ($('.gh-select-single:checked').length) {
                buildBatchDateObject();
            }
        });

        // Week checkbox related events
        $('body').on('change', '#gh-batch-edit-date-picker-container input', batchEditDateWeeks);
        $('body').on('focus', '#gh-batch-edit-date-picker-container input', focusEditDateWeeks);
        $('body').on('blur', '#gh-batch-edit-date-picker-container input', blurEditDateWeeks);

        // Time selectors
        $('body').on('focus click', '#gh-batch-edit-day-picker', function() {
            $(this).attr('data-prev', $(this).val());
        });
        $('body').on('change', '#gh-batch-edit-day-picker', batchEditTime);
        $('body').on('change', '#gh-batch-edit-hours-start', batchEditTime);
        $('body').on('change', '#gh-batch-edit-hours-end', batchEditTime);
        $('body').on('change', '#gh-batch-edit-minutes-start', batchEditTime);
        $('body').on('change', '#gh-batch-edit-minutes-end', batchEditTime);

        // Adding a new day
        $('body').on('click', '.gh-batch-edit-date-add-day', addAnotherDay);
    };

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


<commit_before>/*
Copyright 2020 The Matrix.org Foundation C.I.C.

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

const {assertNoToasts, acceptToast, rejectToast} = require("../usecases/toasts");

module.exports = async function toastScenarios(alice, bob) {
    console.log(" checking and clearing toasts:");

    alice.log.startGroup(`clears toasts`);
    alice.log.step(`reject desktop notifications toast`);
    await rejectToast(alice, "Notifications");
    alice.log.done();

    alice.log.step(`accepts rebrand toast`);
    await acceptToast(alice, "Riot is now Element!");
    const doneButton = await alice.query('.mx_Dialog_primary');
    await doneButton.click(); // also accept the resulting dialog
    alice.log.done();

    alice.log.step(`accepts analytics toast`);
    await acceptToast(alice, "Help us improve Element");
    alice.log.done();

    alice.log.step(`checks no remaining toasts`);
    await assertNoToasts(alice);
    alice.log.done();
    alice.log.endGroup();

    bob.log.startGroup(`clears toasts`);
    bob.log.step(`reject desktop notifications toast`);
    await rejectToast(bob, "Notifications");
    bob.log.done();

    bob.log.step(`reject analytics toast`);
    await rejectToast(bob, "Help us improve Element!");
    bob.log.done();

    bob.log.step(`checks no remaining toasts`);
    await assertNoToasts(bob);
    bob.log.done();
    bob.log.endGroup();
};
<commit_msg>Bob needs to accept the toast too<commit_after>/*
Copyright 2020 The Matrix.org Foundation C.I.C.

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

const {assertNoToasts, acceptToast, rejectToast} = require("../usecases/toasts");

module.exports = async function toastScenarios(alice, bob) {
    console.log(" checking and clearing toasts:");

    alice.log.startGroup(`clears toasts`);
    alice.log.step(`reject desktop notifications toast`);
    await rejectToast(alice, "Notifications");
    alice.log.done();

    alice.log.step(`accepts rebrand toast`);
    await acceptToast(alice, "Riot is now Element!");
    let doneButton = await alice.query('.mx_Dialog_primary');
    await doneButton.click(); // also accept the resulting dialog
    alice.log.done();

    alice.log.step(`accepts analytics toast`);
    await acceptToast(alice, "Help us improve Element");
    alice.log.done();

    alice.log.step(`checks no remaining toasts`);
    await assertNoToasts(alice);
    alice.log.done();
    alice.log.endGroup();

    bob.log.startGroup(`clears toasts`);
    bob.log.step(`reject desktop notifications toast`);
    await rejectToast(bob, "Notifications");
    bob.log.done();

    bob.log.step(`accepts rebrand toast`);
    await acceptToast(bob, "Riot is now Element!");
    doneButton = await bob.query('.mx_Dialog_primary');
    await doneButton.click(); // also accept the resulting dialog
    bob.log.done();

    bob.log.step(`reject analytics toast`);
    await rejectToast(bob, "Help us improve Element!");
    bob.log.done();

    bob.log.step(`checks no remaining toasts`);
    await assertNoToasts(bob);
    bob.log.done();
    bob.log.endGroup();
};
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
  Schema = mongoose.Schema;

/**
 * Article Schema
 */
var CharacterSchema = new Schema({
  created: {
    type: Date,
    default: Date.now
  },
  name: {
    type: String,
    required: true,
    trim: true
  },
  characterClass: {
    type: Number,
    ref: 'Class'
  },
  gender: {
    type: String,
    required: true
  },
  user: {
    type: Schema.ObjectId,
    ref: 'User',
    required: true,
    unique: true
  }
});

/**
 * Validations
 */
CharacterSchema.path('name').validate(function(name) {
  return !!name;
}, 'Name cannot be blank');

CharacterSchema.path('characterClass').validate(function(characterClass) {
  return !!characterClass;
}, 'Class cannot be blank');

/**
 * Statics
 */
CharacterSchema.statics.load = function(id, cb) {
  this.findOne({
    user: id
  }).populate('user', 'name username').exec(cb);
};

mongoose.model('Character', CharacterSchema);
<commit_msg>Updated character model to include reference to the characterClass object.<commit_after>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
  Schema = mongoose.Schema;

/**
 * Article Schema
 */
var CharacterSchema = new Schema({
  created: {
    type: Date,
    default: Date.now
  },
  name: {
    type: String,
    required: true,
    trim: true
  },
  characterClass: {
    type: Schema.ObjectId,
    ref: 'CharacterClass',
    required: true
  },
  gender: {
    type: String,
    required: true
  },
  user: {
    type: Schema.ObjectId,
    ref: 'User',
    required: true,
    unique: true
  }
});

/**
 * Validations
 */
CharacterSchema.path('name').validate(function(name) {
  return !!name;
}, 'Name cannot be blank');

CharacterSchema.path('characterClass').validate(function(characterClass) {
  return !!characterClass;
}, 'Class cannot be blank');

/**
 * Statics
 */
CharacterSchema.statics.load = function(id, cb) {
  this.findOne({
    user: id
  }).populate('user', 'name username').exec(cb);
};

mongoose.model('Character', CharacterSchema);
<|endoftext|>


<commit_before>'use strict'

import Reporter from 'chiffchaff-reporter'
import Promise from 'bluebird'
import Bottleneck from 'bottleneck'
import express from 'express'
import socketIo from 'socket.io'
import defaults from 'defaults'
import open from 'open'
import path from 'path'
import http from 'http'

export default class WwwReporter extends Reporter {
  constructor (options) {
    super()
    this._options = defaults(options, {port: 3000, updateInterval: 250})
    this._lastData = []
    this._disposed = false
    this._limiter = new Bottleneck(1, this._options.updateInterval)
    this._boundReport = this._scheduledReport.bind(this)
  }

  start () {
    this._createApp()
    this._createServer()
    this._createIo()
    return this._listen()
      .then(() => Promise.all([this._openBrowser(), this._waitForConnection()]))
  }

  report (data) {
    if (this._disposed) {
      throw new Error('Reporter disposed')
    }
    this._lastData = data
    this._scheduleReport()
  }

  dispose () {
    return new Promise(resolve => {
      this._disposed = true
      this._scheduleReport()
      resolve()
    })
  }

  _scheduleReport () {
    this._limiter.schedule(this._boundReport)
  }

  _scheduledReport () {
    return new Promise(resolve => {
      this._io.emit('data', this._lastData)
      if (this._disposed) {
        this._completeDisposal()
      }
      resolve()
    })
  }

  _completeDisposal () {
    this._limiter.stopAll()
    this._server.close()
    this._limiter = null
    this._app = null
    this._server = null
    this._io = null
  }

  _createApp () {
    const root = path.resolve(__dirname, '..')
    this._app = express()
    this._app.use(express.static(path.resolve(root, 'public')))
  }

  _createServer () {
    this._server = http.Server(this._app)
  }

  _createIo () {
    this._io = socketIo(this._server)
    this._io.on('connection', socket => this._onConnection(socket))
  }

  _listen () {
    return Promise.promisify(this._server.listen).call(this._server, this._options.port)
  }

  _openBrowser () {
    open(`http://localhost:${this._options.port}/`)
  }

  _waitForConnection () {
    return new Promise(resolve => this._io.on('connection', resolve))
  }

  _onConnection (socket) {
    socket.emit('init', this._lastData)
  }
}
<commit_msg>Restoring awaiting of close<commit_after>'use strict'

import Reporter from 'chiffchaff-reporter'
import Promise from 'bluebird'
import Bottleneck from 'bottleneck'
import express from 'express'
import socketIo from 'socket.io'
import defaults from 'defaults'
import open from 'open'
import path from 'path'
import http from 'http'

export default class WwwReporter extends Reporter {
  constructor (options) {
    super()
    this._options = defaults(options, {port: 3000, updateInterval: 250})
    this._lastData = []
    this._disposed = false
    this._limiter = new Bottleneck(1, this._options.updateInterval)
    this._boundReport = this._scheduledReport.bind(this)
  }

  start () {
    this._createApp()
    this._createServer()
    this._createIo()
    return this._listen()
      .then(() => Promise.all([this._openBrowser(), this._waitForConnection()]))
  }

  report (data) {
    if (this._disposed) {
      throw new Error('Reporter disposed')
    }
    this._lastData = data
    this._scheduleReport()
  }

  dispose () {
    return new Promise(resolve => {
      this._disposed = true
      this._scheduleReport()
      resolve()
    })
  }

  _scheduleReport () {
    this._limiter.schedule(this._boundReport)
  }

  _scheduledReport () {
    return new Promise(resolve => {
      this._io.emit('data', this._lastData)
      resolve()
    }).then(() => this._disposed && this._completeDisposal())
  }

  _completeDisposal () {
    this._limiter.stopAll()
    const closing = Promise.promisify(this._server.close).call(this._server)
    this._io.close()
    this._limiter = null
    this._app = null
    this._server = null
    this._io = null
    return closing
  }

  _createApp () {
    const root = path.resolve(__dirname, '..')
    this._app = express()
    this._app.use(express.static(path.resolve(root, 'public')))
  }

  _createServer () {
    this._server = http.Server(this._app)
  }

  _createIo () {
    this._io = socketIo(this._server)
    this._io.on('connection', socket => this._onConnection(socket))
  }

  _listen () {
    return Promise.promisify(this._server.listen).call(this._server, this._options.port)
  }

  _openBrowser () {
    open(`http://localhost:${this._options.port}/`)
  }

  _waitForConnection () {
    return new Promise(resolve => this._io.on('connection', resolve))
  }

  _onConnection (socket) {
    socket.emit('init', this._lastData)
  }
}
<|endoftext|>


<commit_before>/**!
 *
 * Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
 * @private
 */

import {MemoryStoreAdapter} from '@cisospark/spark-core';
import LocalStorageStoreAdapter from '@ciscospark/storage-adapter-local-storage';

export default {
  boundedAdapter: new LocalStorageStoreAdapter(`ciscospark`),
  unboundedAdapter: MemoryStoreAdapter
};
<commit_msg>fix(ciscospark): spell module name correctly<commit_after>/**!
 *
 * Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
 * @private
 */

import {MemoryStoreAdapter} from '@ciscospark/spark-core';
import LocalStorageStoreAdapter from '@ciscospark/storage-adapter-local-storage';

export default {
  boundedAdapter: new LocalStorageStoreAdapter(`ciscospark`),
  unboundedAdapter: MemoryStoreAdapter
};
<|endoftext|>


<commit_before>import Matrix4 from '../../math/Matrix4.js';
import GestureObserver from '../../input/GestureObserver.js';

export default class OrbitController {
  constructor({
    matrix = new Matrix4(),
    domElement = window,
    pan = 0,
    tilt = 0,
    invertRotation = true,
    distance = 1,
    distanceMin = 0,
    distanceMax = Infinity,
    tiltMin = -Infinity,
    tiltMax = Infinity,
    tiltDisabled = false,
    panMin = -Infinity,
    panMax = Infinity,
    panDisabled = false,
    rotationEasing = .1,
    rotationVelocity = .005,
    zoomEasing = .1,
    zoomVelocity = .1,
    zoomDisabled = false,
  }) {
    this.matrix = matrix;
    this.invertRotation = invertRotation;
    this.distanceMax = distanceMax;
    this.distanceMin = distanceMin;
    this.zoomEasing = zoomEasing;
    this.tiltMax = tiltMax;
    this.tiltMin = tiltMin;
    this.tiltDisabled = tiltDisabled;
    this.panMax = panMax;
    this.panMin = panMin;
    this.panDisabled = panDisabled;
    this.rotationEasing = rotationEasing;
    this.rotationVelocity = rotationVelocity;
    this.zoomDisabled = zoomDisabled;
    this.zoomVelocity = zoomVelocity;

    this.baseMatrix = new Matrix4(this.matrix);

    this._distance = distance;
    this._tilt = tilt;
    this._pan = pan;

    this._panEased = this._pan;
    this._tiltEased = this._tilt;
    this._distanceEased = this._distance;

    this._multiTouchMode = false;

    domElement.addEventListener('wheel', (event) => {
      if (this.zoomDisabled) return;
      this._distance *= 1 + event.deltaY * this.zoomVelocity * .01;
      this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
    }, { passive: true });

    const gestureObserver = new GestureObserver((gesture) => {
      if (!this.panDisabled) {
        this._pan += (this.invertRotation ? -1 : 1) * gesture.movementX * rotationVelocity;
        this._pan = Math.max(this.panMin, Math.min(this.panMax, this._pan));
      }

      if (!this.tiltDisabled) {
        this._tilt += (this.invertRotation ? 1 : -1) * gesture.movementY * rotationVelocity;
        this._tilt = Math.max(this.tiltMin, Math.min(this.tiltMax, this._tilt));
      }

      if (!this.zoomDisabled) {
        this._distance /= 1 + gesture.movementScale * this.zoomVelocity * .01;
        this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
      }
    });
    gestureObserver.observe(domElement);

    this.update();
  }

  get pan() {
    return this._pan;
  }

  set pan(value) {
    this._pan = value;
    this._panEased = value;
  }

  get tilt() {
    return this._tilt;
  }

  set tilt(value) {
    this._tilt = value;
    this._tiltEased = value;
  }

  get distance() {
    return this._distance;
  }

  set distance(value) {
    this._distance = value;
    this._distanceEased = value;
  }

  get panEased() {
    return this._panEased;
  }

  get tiltEased() {
    return this._tiltEased;
  }

  get distanceEased() {
    return this._distanceEased;
  }

  update() {
    this._tiltEased += (this._tilt - this._tiltEased) * this.rotationEasing;
    this._panEased += (this._pan - this._panEased) * this.rotationEasing;
    this._distanceEased += (this._distance - this._distanceEased) * this.zoomEasing;

    this.matrix.identity();
    this.matrix.rotateY(this._panEased);
    this.matrix.rotateX(-this._tiltEased);
    const sinPan = Math.sin(this._panEased);
    const cosPan = Math.cos(this._panEased);
    const cosTilt = Math.cos(this._tiltEased);
    const sinTilt = Math.sin(this._tiltEased);
    this.matrix.x = this._distanceEased * sinPan * cosTilt;
    this.matrix.y = sinTilt * this._distanceEased;
    this.matrix.z = this._distanceEased * cosPan * cosTilt;

    this.matrix.multiply(this.baseMatrix);
  }
}
<commit_msg>fix zoom when initial distance is 0<commit_after>import Matrix4 from '../../math/Matrix4.js';
import GestureObserver from '../../input/GestureObserver.js';

export default class OrbitController {
  constructor({
    matrix = new Matrix4(),
    domElement = window,
    pan = 0,
    tilt = 0,
    invertRotation = true,
    distance = 1,
    distanceMin = 0,
    distanceMax = Infinity,
    tiltMin = -Infinity,
    tiltMax = Infinity,
    tiltDisabled = false,
    panMin = -Infinity,
    panMax = Infinity,
    panDisabled = false,
    rotationEasing = .1,
    rotationVelocity = .005,
    zoomEasing = .1,
    zoomVelocity = .1,
    zoomDisabled = false,
  }) {
    this.matrix = matrix;
    this.invertRotation = invertRotation;
    this.distanceMax = distanceMax;
    this.distanceMin = distanceMin;
    this.zoomEasing = zoomEasing;
    this.tiltMax = tiltMax;
    this.tiltMin = tiltMin;
    this.tiltDisabled = tiltDisabled;
    this.panMax = panMax;
    this.panMin = panMin;
    this.panDisabled = panDisabled;
    this.rotationEasing = rotationEasing;
    this.rotationVelocity = rotationVelocity;
    this.zoomDisabled = zoomDisabled;
    this.zoomVelocity = zoomVelocity;

    this.baseMatrix = new Matrix4(this.matrix);

    this._distance = distance;
    this._tilt = tilt;
    this._pan = pan;

    this._panEased = this._pan;
    this._tiltEased = this._tilt;
    this._distanceEased = this._distance;

    this._multiTouchMode = false;

    domElement.addEventListener('wheel', (event) => {
      if (this.zoomDisabled) return;
      this._distance = Math.max(this._distance, .001) * (1 + event.deltaY * this.zoomVelocity * .01);
      this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
    }, { passive: true });

    const gestureObserver = new GestureObserver((gesture) => {
      if (!this.panDisabled) {
        this._pan += (this.invertRotation ? -1 : 1) * gesture.movementX * rotationVelocity;
        this._pan = Math.max(this.panMin, Math.min(this.panMax, this._pan));
      }

      if (!this.tiltDisabled) {
        this._tilt += (this.invertRotation ? 1 : -1) * gesture.movementY * rotationVelocity;
        this._tilt = Math.max(this.tiltMin, Math.min(this.tiltMax, this._tilt));
      }

      if (!this.zoomDisabled) {
        this._distance /= 1 + gesture.movementScale * this.zoomVelocity * .01;
        this._distance = Math.max(this.distanceMin, Math.min(this.distanceMax, this._distance));
      }
    });
    gestureObserver.observe(domElement);

    this.update();
  }

  get pan() {
    return this._pan;
  }

  set pan(value) {
    this._pan = value;
    this._panEased = value;
  }

  get tilt() {
    return this._tilt;
  }

  set tilt(value) {
    this._tilt = value;
    this._tiltEased = value;
  }

  get distance() {
    return this._distance;
  }

  set distance(value) {
    this._distance = value;
    this._distanceEased = value;
  }

  get panEased() {
    return this._panEased;
  }

  get tiltEased() {
    return this._tiltEased;
  }

  get distanceEased() {
    return this._distanceEased;
  }

  update() {
    this._tiltEased += (this._tilt - this._tiltEased) * this.rotationEasing;
    this._panEased += (this._pan - this._panEased) * this.rotationEasing;
    this._distanceEased += (this._distance - this._distanceEased) * this.zoomEasing;

    this.matrix.identity();
    this.matrix.rotateY(this._panEased);
    this.matrix.rotateX(-this._tiltEased);
    const sinPan = Math.sin(this._panEased);
    const cosPan = Math.cos(this._panEased);
    const cosTilt = Math.cos(this._tiltEased);
    const sinTilt = Math.sin(this._tiltEased);
    this.matrix.x = this._distanceEased * sinPan * cosTilt;
    this.matrix.y = sinTilt * this._distanceEased;
    this.matrix.z = this._distanceEased * cosPan * cosTilt;

    this.matrix.multiply(this.baseMatrix);
  }
}
<|endoftext|>


<commit_before>const readline = require('readline');
const fs = require('fs');
const wordnet = require('wordnet');

function isUpperCase(line) {
  return line === line.toUpperCase();
}

const input = readline.createInterface({
  input: fs.createReadStream(process.argv[2])
});

var currentLine = '';
var lines = [];

input.on('line', line => {
  lines.push(line.trim());
});

function getSentences(paragraph) {
  var sentences = [];
  var currentSentence = '';
  var words = paragraph.split(' ');
  let word;
  while (word = words.shift()) {
    if (!word) {
      continue;
    }
    currentSentence += word + ' ';
    if ((word.endsWith('.') || word.endsWith('!') || word.endsWith('?')) &&
      word !== 'Mr.' && word !== 'Mrs.') {
      sentences.push(currentSentence.trim());
      currentSentence = '';
    }
  }
  return sentences;
}

/*
function shuffle(array) {
  var rand, index = -1,
    length = array.length,
    result = Array(length);
  while (++index < length) {
    rand = Math.floor(Math.random() * (index + 1));
    result[index] = result[rand];
    result[rand] = array[index];
  }
  return result;
}
  */

var finalSentences = [];

input.on('close', () => {
  lines.forEach(line => {
    finalSentences = finalSentences.concat(getSentences(line));
  });
  finalSentences = finalSentences.filter(s => {
    if (s.includes(',') || s.includes('–') ||
      s.includes('..') || s.includes('-') ||
      s.includes(')') || s.includes('\' ') || s.includes('de ')) {
      return false;
    }

    var words = s.split(' ');
    // words.forEach(w => {
    //   try {
    //     w && wordnet.lookup(w, (err, def) => {
    //       if (err) {
    //         console.log('error', w, err);
    //       }
    //     });
    //   } catch (err) {
    //     console.log('bad word', w, err);
    //   }
    // });

    var spaces = words.length - 1;
    if (spaces < 3 || spaces > 10) {
      return false;
    }

    return true;
  });
  finalSentences.forEach(sentence => {
    process.stdout.write(sentence + '\n');
  });
});

<commit_msg>fixing extraction tool<commit_after>t readline = require('readline');
const fs = require('fs');
const crypto = require('crypto');

function hash(data) {
  return crypto.createHash("sha256").update(data).digest("base64");
}

const input = readline.createInterface({
  input: fs.createReadStream(process.argv[2])
});

var lines = {};

input.on('line', line => {
  lines[hash(line)] = line;
});

input.on('close', () => {
  console.log(JSON.stringify(lines, null, 2));
});

<|endoftext|>


<commit_before>/* Copyright 2015, Yahoo Inc.
   Copyrights licensed under the MIT License.
   See the accompanying LICENSE file for terms. */

var React = require('react');
var Scroller = require('../../src/scroller');
var ScrollItem = require('../../src/scroll-item');

var Lorem = require('../lorem');
var Header = require('./header');

var FancyHeader = React.createClass({

  getContentSize: function (items, scroller) {
    return {
      width: items.content.rect.width,
      height: items.content.rect.height + items.background.rect.height
    };
  },

  render: function () {
    return (
      <Scroller viewport scrollingX={false} scrollingY={true} getContentSize={this.getContentSize}>
        <ScrollItem name="background" scrollHandler={handler}>

        </ScrollItem>
        <ScrollItem name="white" scrollHandler={handler}>

        </ScrollItem>
        <ScrollItem name="transparent" scrollHandler={handler}>

        </ScrollItem>
        <ScrollItem name="content" scrollHandler={handler}>
          <Lorem />
        </ScrollItem>
      </Scroller>
    );
  }

});

/*
  Notice: It's a Scrollable best practice to use plain functions instead
  of React bound methods. Read more about why on the minimal example.
  */
function handler(x, y, self, items, scroller) {
  var transitionPixels = 100;
  var ratio = 6;
  var headerPos = Math.max(transitionPixels - y, 0) / ratio;
  if (y < 0) {
    headerPos = transitionPixels / ratio;
  }

  switch (self.props.name) {
    case "content":
      return {
        zIndex: 3,
        y: -y + items.background.rect.height,
      };
    case "white":
      return {
        opacity: Math.min(1/transitionPixels * y, 1),
        zIndex: 5,
        y: headerPos,
      };
    case "transparent":
      return {
        zIndex: 4,
        y: headerPos,
      };
    case "background":
      return {
        scale: Math.max(1, 1 - (y / 400)),
        zIndex: 2,
        y: Math.min(0, -y),
      };
    default:
      // during development, if I create a new <ScrollItem> this is handy
      // as it will sticky this element to the bottom of the <Scroller>
      return {
        zIndex: 10,
        y: scroller.rect.height - self.rect.height,
      };
  }
}

module.exports = FancyHeader;
<commit_msg>Add micro-optimization for fancy-header example to avoid painting<commit_after>/* Copyright 2015, Yahoo Inc.
   Copyrights licensed under the MIT License.
   See the accompanying LICENSE file for terms. */

var React = require('react');
var Scroller = require('../../src/scroller');
var ScrollItem = require('../../src/scroll-item');

var Lorem = require('../lorem');
var Header = require('./header');

var FancyHeader = React.createClass({

  getContentSize: function (items, scroller) {
    return {
      width: items.content.rect.width,
      height: items.content.rect.height + items.background.rect.height
    };
  },

  render: function () {
    return (
      <Scroller viewport scrollingX={false} scrollingY={true} getContentSize={this.getContentSize}>
        <ScrollItem name="background" scrollHandler={handler}>

        </ScrollItem>
        <ScrollItem name="white" scrollHandler={handler}>

        </ScrollItem>
        <ScrollItem name="transparent" scrollHandler={handler}>

        </ScrollItem>
        <ScrollItem name="content" scrollHandler={handler}>
          <Lorem />
        </ScrollItem>
      </Scroller>
    );
  }

});

/*
  Notice: It's a Scrollable best practice to use plain functions instead
  of React bound methods. Read more about why on the minimal example.
  */
function handler(x, y, self, items, scroller) {
  var transitionPixels = 100;
  var ratio = 6;
  var headerPos = Math.max(transitionPixels - y, 0) / ratio;
  if (y < 0) {
    headerPos = transitionPixels / ratio;
  }

  switch (self.props.name) {
    case "content":
      return {
        zIndex: 3,
        y: -y + items.background.rect.height,
      };
    case "white":
      return {
        // this rounding to 0.001 and 0.9999 should not be needed.
        // some browsers were causing re-paint. So I will leave this here
        // as documentation
        opacity: Math.max(0.001, Math.min(1/transitionPixels * y, 0.9999)),
        zIndex: 5,
        y: headerPos,
      };
    case "transparent":
      return {
        zIndex: 4,
        y: headerPos,
      };
    case "background":
      return {
        scale: Math.max(1, 1 - (y / 400)),
        zIndex: 2,
        y: Math.min(0, -y),
      };
    default:
      // during development, if I create a new <ScrollItem> this is handy
      // as it will sticky this element to the bottom of the <Scroller>
      return {
        zIndex: 10,
        y: scroller.rect.height - self.rect.height,
      };
  }
}

module.exports = FancyHeader;
<|endoftext|>