<commit_before>'use strict';

/**
 * Main OpenVeo module.
 *
 * Loads all AngularJS dependencies and plugins modules. It also initializes formly and define routes exposed
 * by OpenVeo core.
 *
 * @module ov
 * @main ov
 */

(function(angular) {

  var moduleDependencies = [
    'ngRoute',
    'ov.authentication',
    'ov.storage',
    'ov.i18n',
    'ov.entity',
    'ov.alert',
    'ov.tableForm',
    'ov.utilService',
    'ov.socket',
    'ui.bootstrap',
    'ui.tree',
    'ngTasty',
    'formly',
    'formlyBootstrap',
    'ngJSONPath',
    'ngAnimate',
    'checklist-model',
    'ui.tinymce',
    'ngFileUpload'
  ];

  // Loads all openveo sub plugins as dependencies of the module "ov"
  if (typeof openVeoSettings !== 'undefined' && openVeoSettings.plugins) {
    angular.forEach(openVeoSettings.plugins, function(pluginToLoad) {

      // If a module exists for a sub plugin
      // e.g ov.plugin1
      // Load it.
      angular.module('ov.' + pluginToLoad);
      moduleDependencies.push('ov.' + pluginToLoad);

    });
  }

  var app = angular.module('ov', moduleDependencies);
  app.run(['formlyConfig', '$filter', '$sce', function(formlyConfig, $filter, $sce) {

    // Formly wrappers
    formlyConfig.setWrapper({
      name: 'collapse',
      templateUrl: 'ov-core-collapse.html'
    });
    formlyConfig.setWrapper({
      name: 'horizontalBootstrapLabel',
      templateUrl: 'ov-core-horizontal-bootstrap-label.html'
    });
    formlyConfig.setWrapper({
      name: 'horizontalBootstrapLabelOnly',
      templateUrl: 'ov-core-horizontal-bootstrap-label-only.html'
    });
    formlyConfig.setWrapper({
      name: 'editableWrapper',
      templateUrl: 'ov-core-editable-wrapper.html'
    });

    // Formly types
    formlyConfig.setType({
      name: 'datepicker',
      templateUrl: 'ov-core-formly-datepicker.html',
      defaultOptions: {
        validation: {
          show: true
        }
      },
      link: function(scope, element, attrs) {
        scope.status = {opened: false};

        scope.open = function() {
          scope.status.opened = true;
        };
      }
    });
    formlyConfig.setType({
      name: 'tags',
      templateUrl: 'ov-core-formly-tags.html',
      defaultOptions: {
        validation: {
          show: true
        }
      }
    });
    formlyConfig.setType({
      name: 'match',
      templateUrl: 'ov-core-formly-match.html',
      defaultOptions: {
        validation: {
          show: true
        }
      }
    });
    formlyConfig.setType({
      name: 'emptyrow',
      templateUrl: 'ov-core-empty-row.html',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'section',
      templateUrl: 'ov-core-section.html'
    });
    formlyConfig.setType({
      extends: 'input',
      name: 'editableInput',
      link: function(scope, element, attrs) {
        scope.show = function() {
          scope.isEmpty = scope.model[scope.options.key] ? false : true;
          return scope.model[scope.options.key] || $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      extends: 'select',
      name: 'editableSelect',
      link: function(scope, element, attrs) {
        scope.show = function() {
          var labels = [];
          var values;

          if (Object.prototype.toString.call(scope.model[scope.options.key]) === '[object Array]')
            values = scope.model[scope.options.key];
          else
            values = [scope.model[scope.options.key]];

          // Find selected option label
          scope.to.options.forEach(function(option) {
            values.forEach(function(value) {
              if (option.value === value)
                labels.push(option.name);
            });
          });

          scope.isEmpty = labels.length ? false : true;
          return labels.length ? labels.join(', ') : $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      name: 'editableDatepicker',
      extends: 'datepicker',
      link: function(scope, element, attrs) {
        scope.show = function() {
          return $filter('date')(scope.model[scope.options.key], 'shortDate');
        };
      }
    });
    formlyConfig.setType({
      name: 'editableTags',
      extends: 'tags',
      wrapper: ['editableTagsWrapper'],
      link: function(scope, element, attrs) {
        scope.show = function() {
          var tags = scope.model[scope.options.key];
          scope.isEmpty = tags && tags.length ? false : true;
          return tags && tags.join(', ') || $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      name: 'ovMultiCheckBox',
      templateUrl: 'ov-core-formly-multi-check-box.html'
    });
    formlyConfig.setType({
      name: 'ovEditableMultiCheckBox',
      extends: 'ovMultiCheckBox',
      link: function(scope) {
        scope.show = function() {
          var selected = [];
          angular.forEach(scope.to.options, function(s) {
            if (scope.model[scope.options.key] && scope.model[scope.options.key].indexOf(s.id) >= 0) {
              selected.push(s.name);
            }
          });
          scope.isEmpty = selected.length ? false : true;
          return selected.length ? selected.join(', ') : $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      extends: 'checkbox',
      name: 'editableCheckbox',
      link: function(scope, element, attrs) {
        scope.show = function() {
          scope.isEmpty = false;
          return scope.model[scope.options.key] && 'CORE.UI.TRUE' || 'CORE.UI.FALSE';
        };
      }
    });
    formlyConfig.setType({
      name: 'ovTinymce',
      templateUrl: 'ov-core-textarea-tinymce.html'
    });
    formlyConfig.setType({
      name: 'ovEditableTinymce',
      extends: 'ovTinymce',
      link: function(scope) {
        scope.show = function() {
          scope.isEmpty = scope.model[scope.options.key] ? false : true;
          return scope.model[scope.options.key] || $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      name: 'ovFile',
      templateUrl: 'ov-file.html',
      link: function(scope) {

        // As a File object can't be copied the model of the ovFile type is not
        // the file but the progress of the upload (in percent)
        // File model is stored here
        scope.file = null;

        // Watch for model changes (progress of the upload)
        scope.$watch('model["' + scope.options.key + '"]', function(progress) {
          if (progress === 100) {

            // Done uploading file
            // Reset field
            scope.file = null;
            scope.form.file.$setUntouched();
            scope.form.file.$setPristine();

          }
        });
      }
    });
    formlyConfig.setType({
      name: 'simple',
      templateUrl: 'ov-core-simple.html',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalFile',
      extends: 'ovFile',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableFile',
      extends: 'horizontalFile',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalTinymce',
      extends: 'ovTinymce',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableTinymce',
      extends: 'ovEditableTinymce',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalInput',
      extends: 'input',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableInput',
      extends: 'editableInput',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalDatepicker',
      extends: 'datepicker',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableDatepicker',
      extends: 'editableDatepicker',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalSelect',
      extends: 'select',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableSelect',
      extends: 'editableSelect',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalMultiCheckbox',
      extends: 'ovMultiCheckBox',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableMultiCheckbox',
      extends: 'ovEditableMultiCheckBox',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalTags',
      extends: 'tags',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalMatch',
      extends: 'match',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableTags',
      extends: 'editableTags',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalCheckbox',
      extends: 'checkbox',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableCheckbox',
      extends: 'editableCheckbox',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });

  }]);

  /**
   * Configures application main routes and set location mode to HTML5.
   */
  app.config(['$routeProvider', '$locationProvider', '$httpProvider',
    function($routeProvider, $locationProvider, $httpProvider) {
      var strategies = openVeoSettings.authenticationStrategies;
      var hasLdap = openVeoSettings.authenticationMechanisms.indexOf(strategies.LDAP) >= 0;
      var hasCas = openVeoSettings.authenticationMechanisms.indexOf(strategies.CAS) >= 0;

      // Register / route with authentication
      $routeProvider.when('/', {
        templateUrl: 'views/home.html',
        controller: 'HomeController',
        title: 'CORE.HOME.PAGE_TITLE'
      });

      // Register /login route without authentication
      $routeProvider.when('/login', {
        title: 'CORE.LOGIN.PAGE_TITLE',
        templateUrl: 'views/login.html',
        controller: 'LoginController',
        resolve: {
          i18nCommon: ['i18nService', function(i18nService) {
            return i18nService.addDictionary('common');
          }],
          i18nLogin: ['i18nService', function(i18nService) {
            return i18nService.addDictionary('login');
          }]
        }
      }).otherwise('/');

      // Register /applications route with authentication
      // Also retrieve the list of applications
      $routeProvider.when('/applications-list', {
        templateUrl: 'views/applications.html',
        controller: 'ApplicationController',
        title: 'CORE.APPLICATIONS.PAGE_TITLE',
        access: 'core-access-applications-page',
        resolve: {
          scopes: ['applicationService', function(applicationService) {
            return applicationService.loadScopes();
          }]
        }
      });

      // Register /users route with authentication
      // Also retrieve the list of roles
      $routeProvider.when('/users-list', {
        templateUrl: 'views/users.html',
        controller: 'UserController',
        title: 'CORE.USERS.PAGE_TITLE',
        access: 'core-access-users-page',
        resolve: {
          roles: ['entityService', function(entityService) {
            return entityService.getAllEntities('roles');
          }]
        }
      });

      // Register /profiles route with authentication
      // Also retrieve the user profile
      $routeProvider.when('/profile', {
        templateUrl: 'views/profile.html',
        controller: 'ProfileController',
        controllerAs: 'ctrl',
        title: 'CORE.PROFILES.PAGE_TITLE',
        resolve: {
          user: ['authenticationService', function(authenticationService) {
            return authenticationService.getUserInfo();
          }]
        }
      });

      // Register /roles route with authentication
      // Also retrieve the list of permissions
      $routeProvider.when('/roles-list', {
        templateUrl: 'views/roles.html',
        controller: 'RoleController',
        title: 'CORE.ROLES.PAGE_TITLE',
        access: 'core-access-roles-page',
        resolve: {
          permissions: ['userService', function(userService) {
            return userService.loadPermissions();
          }]
        }
      });

      // Register /groups route with authentication
      $routeProvider.when('/groups-list', {
        templateUrl: 'views/groups.html',
        controller: 'GroupController',
        title: 'CORE.GROUPS.PAGE_TITLE',
        access: 'core-access-groups-page'
      });

      // Register /openveo-settings route with authentication
      // Also retrieve OpenVeo settings and roles
      $routeProvider.when('/openveo-settings', {
        templateUrl: 'views/settings.html',
        controller: 'SettingsController',
        controllerAs: 'ctrl',
        title: 'CORE.SETTINGS.PAGE_TITLE',
        access: 'core-access-settings-page',
        resolve: {
          ldapSettings: ['$q', 'entityService', function($q, entityService) {
            if (!hasLdap) return $q.when({data: {}});
            return entityService.getEntity('settings', null, 'core-' + strategies.LDAP);
          }],
          casSettings: ['$q', 'entityService', function($q, entityService) {
            if (!hasCas) return $q.when({data: {}});
            return entityService.getEntity('settings', null, 'core-' + strategies.CAS);
          }],
          roles: ['entityService', function(entityService) {
            return entityService.getAllEntities('roles');
          }]
        }
      });

      $locationProvider.html5Mode(true);
      $httpProvider.interceptors.push('errorInterceptor');

      // Remove the 300ms delay on touch device
      /* global FastClick */
      FastClick.attach(document.body);
    }]);

  // Replace "classic" spaces with non-breaking-spaces
  app.filter('noBreakSpace', function() {
    return function(value) {
      return value.replace(/ /g, '\u00A0');
    };
  });

})(angular);
<commit_msg>Fix value translation of editable checkboxes<commit_after>'use strict';

/**
 * Main OpenVeo module.
 *
 * Loads all AngularJS dependencies and plugins modules. It also initializes formly and define routes exposed
 * by OpenVeo core.
 *
 * @module ov
 * @main ov
 */

(function(angular) {

  var moduleDependencies = [
    'ngRoute',
    'ov.authentication',
    'ov.storage',
    'ov.i18n',
    'ov.entity',
    'ov.alert',
    'ov.tableForm',
    'ov.utilService',
    'ov.socket',
    'ui.bootstrap',
    'ui.tree',
    'ngTasty',
    'formly',
    'formlyBootstrap',
    'ngJSONPath',
    'ngAnimate',
    'checklist-model',
    'ui.tinymce',
    'ngFileUpload'
  ];

  // Loads all openveo sub plugins as dependencies of the module "ov"
  if (typeof openVeoSettings !== 'undefined' && openVeoSettings.plugins) {
    angular.forEach(openVeoSettings.plugins, function(pluginToLoad) {

      // If a module exists for a sub plugin
      // e.g ov.plugin1
      // Load it.
      angular.module('ov.' + pluginToLoad);
      moduleDependencies.push('ov.' + pluginToLoad);

    });
  }

  var app = angular.module('ov', moduleDependencies);
  app.run(['formlyConfig', '$filter', '$sce', function(formlyConfig, $filter, $sce) {

    // Formly wrappers
    formlyConfig.setWrapper({
      name: 'collapse',
      templateUrl: 'ov-core-collapse.html'
    });
    formlyConfig.setWrapper({
      name: 'horizontalBootstrapLabel',
      templateUrl: 'ov-core-horizontal-bootstrap-label.html'
    });
    formlyConfig.setWrapper({
      name: 'horizontalBootstrapLabelOnly',
      templateUrl: 'ov-core-horizontal-bootstrap-label-only.html'
    });
    formlyConfig.setWrapper({
      name: 'editableWrapper',
      templateUrl: 'ov-core-editable-wrapper.html'
    });

    // Formly types
    formlyConfig.setType({
      name: 'datepicker',
      templateUrl: 'ov-core-formly-datepicker.html',
      defaultOptions: {
        validation: {
          show: true
        }
      },
      link: function(scope, element, attrs) {
        scope.status = {opened: false};

        scope.open = function() {
          scope.status.opened = true;
        };
      }
    });
    formlyConfig.setType({
      name: 'tags',
      templateUrl: 'ov-core-formly-tags.html',
      defaultOptions: {
        validation: {
          show: true
        }
      }
    });
    formlyConfig.setType({
      name: 'match',
      templateUrl: 'ov-core-formly-match.html',
      defaultOptions: {
        validation: {
          show: true
        }
      }
    });
    formlyConfig.setType({
      name: 'emptyrow',
      templateUrl: 'ov-core-empty-row.html',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'section',
      templateUrl: 'ov-core-section.html'
    });
    formlyConfig.setType({
      extends: 'input',
      name: 'editableInput',
      link: function(scope, element, attrs) {
        scope.show = function() {
          scope.isEmpty = scope.model[scope.options.key] ? false : true;
          return scope.model[scope.options.key] || $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      extends: 'select',
      name: 'editableSelect',
      link: function(scope, element, attrs) {
        scope.show = function() {
          var labels = [];
          var values;

          if (Object.prototype.toString.call(scope.model[scope.options.key]) === '[object Array]')
            values = scope.model[scope.options.key];
          else
            values = [scope.model[scope.options.key]];

          // Find selected option label
          scope.to.options.forEach(function(option) {
            values.forEach(function(value) {
              if (option.value === value)
                labels.push(option.name);
            });
          });

          scope.isEmpty = labels.length ? false : true;
          return labels.length ? labels.join(', ') : $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      name: 'editableDatepicker',
      extends: 'datepicker',
      link: function(scope, element, attrs) {
        scope.show = function() {
          return $filter('date')(scope.model[scope.options.key], 'shortDate');
        };
      }
    });
    formlyConfig.setType({
      name: 'editableTags',
      extends: 'tags',
      wrapper: ['editableTagsWrapper'],
      link: function(scope, element, attrs) {
        scope.show = function() {
          var tags = scope.model[scope.options.key];
          scope.isEmpty = tags && tags.length ? false : true;
          return tags && tags.join(', ') || $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      name: 'ovMultiCheckBox',
      templateUrl: 'ov-core-formly-multi-check-box.html'
    });
    formlyConfig.setType({
      name: 'ovEditableMultiCheckBox',
      extends: 'ovMultiCheckBox',
      link: function(scope) {
        scope.show = function() {
          var selected = [];
          angular.forEach(scope.to.options, function(s) {
            if (scope.model[scope.options.key] && scope.model[scope.options.key].indexOf(s.id) >= 0) {
              selected.push(s.name);
            }
          });
          scope.isEmpty = selected.length ? false : true;
          return selected.length ? selected.join(', ') : $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      extends: 'checkbox',
      name: 'editableCheckbox',
      link: function(scope, element, attrs) {
        scope.show = function() {
          scope.isEmpty = false;
          return scope.model[scope.options.key] &&
            $filter('translate')('CORE.UI.TRUE') || $filter('translate')('CORE.UI.FALSE');
        };
      }
    });
    formlyConfig.setType({
      name: 'ovTinymce',
      templateUrl: 'ov-core-textarea-tinymce.html'
    });
    formlyConfig.setType({
      name: 'ovEditableTinymce',
      extends: 'ovTinymce',
      link: function(scope) {
        scope.show = function() {
          scope.isEmpty = scope.model[scope.options.key] ? false : true;
          return scope.model[scope.options.key] || $filter('translate')('CORE.UI.EMPTY');
        };
      }
    });
    formlyConfig.setType({
      name: 'ovFile',
      templateUrl: 'ov-file.html',
      link: function(scope) {

        // As a File object can't be copied the model of the ovFile type is not
        // the file but the progress of the upload (in percent)
        // File model is stored here
        scope.file = null;

        // Watch for model changes (progress of the upload)
        scope.$watch('model["' + scope.options.key + '"]', function(progress) {
          if (progress === 100) {

            // Done uploading file
            // Reset field
            scope.file = null;
            scope.form.file.$setUntouched();
            scope.form.file.$setPristine();

          }
        });
      }
    });
    formlyConfig.setType({
      name: 'simple',
      templateUrl: 'ov-core-simple.html',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalFile',
      extends: 'ovFile',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableFile',
      extends: 'horizontalFile',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalTinymce',
      extends: 'ovTinymce',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableTinymce',
      extends: 'ovEditableTinymce',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalInput',
      extends: 'input',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableInput',
      extends: 'editableInput',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalDatepicker',
      extends: 'datepicker',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableDatepicker',
      extends: 'editableDatepicker',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalSelect',
      extends: 'select',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableSelect',
      extends: 'editableSelect',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalMultiCheckbox',
      extends: 'ovMultiCheckBox',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableMultiCheckbox',
      extends: 'ovEditableMultiCheckBox',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalTags',
      extends: 'tags',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalMatch',
      extends: 'match',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableTags',
      extends: 'editableTags',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalCheckbox',
      extends: 'checkbox',
      wrapper: ['horizontalBootstrapLabel', 'bootstrapHasError']
    });
    formlyConfig.setType({
      name: 'horizontalEditableCheckbox',
      extends: 'editableCheckbox',
      wrapper: ['editableWrapper', 'horizontalBootstrapLabel', 'bootstrapHasError']
    });

  }]);

  /**
   * Configures application main routes and set location mode to HTML5.
   */
  app.config(['$routeProvider', '$locationProvider', '$httpProvider',
    function($routeProvider, $locationProvider, $httpProvider) {
      var strategies = openVeoSettings.authenticationStrategies;
      var hasLdap = openVeoSettings.authenticationMechanisms.indexOf(strategies.LDAP) >= 0;
      var hasCas = openVeoSettings.authenticationMechanisms.indexOf(strategies.CAS) >= 0;

      // Register / route with authentication
      $routeProvider.when('/', {
        templateUrl: 'views/home.html',
        controller: 'HomeController',
        title: 'CORE.HOME.PAGE_TITLE'
      });

      // Register /login route without authentication
      $routeProvider.when('/login', {
        title: 'CORE.LOGIN.PAGE_TITLE',
        templateUrl: 'views/login.html',
        controller: 'LoginController',
        resolve: {
          i18nCommon: ['i18nService', function(i18nService) {
            return i18nService.addDictionary('common');
          }],
          i18nLogin: ['i18nService', function(i18nService) {
            return i18nService.addDictionary('login');
          }]
        }
      }).otherwise('/');

      // Register /applications route with authentication
      // Also retrieve the list of applications
      $routeProvider.when('/applications-list', {
        templateUrl: 'views/applications.html',
        controller: 'ApplicationController',
        title: 'CORE.APPLICATIONS.PAGE_TITLE',
        access: 'core-access-applications-page',
        resolve: {
          scopes: ['applicationService', function(applicationService) {
            return applicationService.loadScopes();
          }]
        }
      });

      // Register /users route with authentication
      // Also retrieve the list of roles
      $routeProvider.when('/users-list', {
        templateUrl: 'views/users.html',
        controller: 'UserController',
        title: 'CORE.USERS.PAGE_TITLE',
        access: 'core-access-users-page',
        resolve: {
          roles: ['entityService', function(entityService) {
            return entityService.getAllEntities('roles');
          }]
        }
      });

      // Register /profiles route with authentication
      // Also retrieve the user profile
      $routeProvider.when('/profile', {
        templateUrl: 'views/profile.html',
        controller: 'ProfileController',
        controllerAs: 'ctrl',
        title: 'CORE.PROFILES.PAGE_TITLE',
        resolve: {
          user: ['authenticationService', function(authenticationService) {
            return authenticationService.getUserInfo();
          }]
        }
      });

      // Register /roles route with authentication
      // Also retrieve the list of permissions
      $routeProvider.when('/roles-list', {
        templateUrl: 'views/roles.html',
        controller: 'RoleController',
        title: 'CORE.ROLES.PAGE_TITLE',
        access: 'core-access-roles-page',
        resolve: {
          permissions: ['userService', function(userService) {
            return userService.loadPermissions();
          }]
        }
      });

      // Register /groups route with authentication
      $routeProvider.when('/groups-list', {
        templateUrl: 'views/groups.html',
        controller: 'GroupController',
        title: 'CORE.GROUPS.PAGE_TITLE',
        access: 'core-access-groups-page'
      });

      // Register /openveo-settings route with authentication
      // Also retrieve OpenVeo settings and roles
      $routeProvider.when('/openveo-settings', {
        templateUrl: 'views/settings.html',
        controller: 'SettingsController',
        controllerAs: 'ctrl',
        title: 'CORE.SETTINGS.PAGE_TITLE',
        access: 'core-access-settings-page',
        resolve: {
          ldapSettings: ['$q', 'entityService', function($q, entityService) {
            if (!hasLdap) return $q.when({data: {}});
            return entityService.getEntity('settings', null, 'core-' + strategies.LDAP);
          }],
          casSettings: ['$q', 'entityService', function($q, entityService) {
            if (!hasCas) return $q.when({data: {}});
            return entityService.getEntity('settings', null, 'core-' + strategies.CAS);
          }],
          roles: ['entityService', function(entityService) {
            return entityService.getAllEntities('roles');
          }]
        }
      });

      $locationProvider.html5Mode(true);
      $httpProvider.interceptors.push('errorInterceptor');

      // Remove the 300ms delay on touch device
      /* global FastClick */
      FastClick.attach(document.body);
    }]);

  // Replace "classic" spaces with non-breaking-spaces
  app.filter('noBreakSpace', function() {
    return function(value) {
      return value.replace(/ /g, '\u00A0');
    };
  });

})(angular);
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification,
//  are permitted provided that the following conditions are met:
//  1. Redistributions of source code must retain the above copyright notice, this
//  list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice,
//  this list of conditions and the following disclaimer in the documentation and/or
//  other materials provided with the distribution.
//  3. Neither the name of the organization nor the names of its contributors may be
//  used to endorse or promote products derived from this software without specific
//  prior written permission.

define(
    ['require', 'module', 'jquery', 'underscore', 'URIjs', './discover_content_type'],
    function (require, module, $, _, URI, ContentTypeDiscovery) {

        var ContentDocumentFetcher = function (publicationFetcher, spineItem, loadedDocumentUri, publicationResourcesCache) {

            var self = this;

            var _contentDocumentPathRelativeToPackage = spineItem.href;
            var _publicationFetcher = publicationFetcher;
            var _contentDocumentText;
            var _srcMediaType = spineItem.media_type;
            var _contentDocumentDom;
            var _publicationResourcesCache = publicationResourcesCache;

            // PUBLIC API

            this.fetchContentDocumentAndResolveDom = function (contentDocumentResolvedCallback, errorCallback) {
                _publicationFetcher.relativeToPackageFetchFileContents(_contentDocumentPathRelativeToPackage, 'text',
                    function (contentDocumentText) {
                        _contentDocumentText = contentDocumentText;
                        self.resolveInternalPackageResources(contentDocumentResolvedCallback, errorCallback);
                    }, errorCallback
                );
            };

            this.resolveInternalPackageResources = function (resolvedDocumentCallback, onerror) {

                _contentDocumentDom = _publicationFetcher.markupParser.parseMarkup(_contentDocumentText, _srcMediaType);
                setBaseUri(_contentDocumentDom, loadedDocumentUri);

                var resolutionDeferreds = [];

                if (_publicationFetcher.shouldFetchMediaAssetsProgrammatically()) {
                    resolveDocumentImages(resolutionDeferreds, onerror);
                    resolveDocumentAudios(resolutionDeferreds, onerror);
                    resolveDocumentVideos(resolutionDeferreds, onerror);
                }
                // TODO: recursive fetching, parsing and DOM construction of documents in IFRAMEs,
                // with CSS preprocessing and obfuscated font handling
                resolveDocumentIframes(resolutionDeferreds, onerror);
                // TODO: resolution (e.g. using DOM mutation events) of scripts loaded dynamically by scripts
                resolveDocumentScripts(resolutionDeferreds, onerror);
                resolveDocumentLinkStylesheets(resolutionDeferreds, onerror);
                resolveDocumentEmbeddedStylesheets(resolutionDeferreds, onerror);

                $.when.apply($, resolutionDeferreds).done(function () {
                    resolvedDocumentCallback(_contentDocumentDom);
                });

            };

            // INTERNAL FUNCTIONS

            function setBaseUri(documentDom, baseURI) {
                var baseElem = documentDom.getElementsByTagName('base')[0];
                if (!baseElem) {
                    baseElem = documentDom.createElement('base');

                    var anchor = documentDom.getElementsByTagName('head')[0];
                    anchor.insertBefore(baseElem, anchor.childNodes[0]);
                }
                baseElem.setAttribute('href', baseURI);
            }

            function _handleError(err) {
                if (err) {
                    if (err.message) {
                        console.error(err.message);
                    }
                    if (err.stack) {
                        console.error(err.stack);
                    }
                }
                console.error(err);
            }

            function fetchResourceForElement(resolvedElem, refAttrOrigVal, refAttr, fetchMode, resolutionDeferreds,
                                             onerror, resourceDataPreprocessing) {
                var resourceUriRelativeToPackageDocument = (new URI(refAttrOrigVal)).absoluteTo(_contentDocumentPathRelativeToPackage).toString();

                var cachedResourceUrl = _publicationResourcesCache.getResourceURL(resourceUriRelativeToPackageDocument);

                function replaceRefAttrInElem(newResourceUrl) {
                    // Store original refAttrVal in a special attribute to provide access to the original href:
                    $(resolvedElem).data('epubZipOrigHref', refAttrOrigVal);
                    $(resolvedElem).attr(refAttr, newResourceUrl);
                }

                if (cachedResourceUrl) {
                    replaceRefAttrInElem(cachedResourceUrl);
                } else {
                    var resolutionDeferred = $.Deferred();
                    resolutionDeferreds.push(resolutionDeferred);

                    _publicationFetcher.relativeToPackageFetchFileContents(resourceUriRelativeToPackageDocument,
                        fetchMode,
                        function (resourceData) {

                            // Generate a function to replace element's resource URL with URL of fetched data.
                            // The function will either be called directly, immediately (if no preprocessing of resourceData is in effect)
                            // or indirectly, later after resourceData preprocessing finishes:
                            var replaceResourceURL = function (finalResourceData) {
                                // Creating an object URL requires a Blob object, so resource data fetched in text mode needs to be wrapped in a Blob:
                                if (fetchMode === 'text') {
                                    var textResourceContentType = ContentTypeDiscovery.identifyContentTypeFromFileName(resourceUriRelativeToPackageDocument);
                                    var declaredType = $(resolvedElem).attr('type');
                                    if (declaredType) {
                                        textResourceContentType = declaredType;
                                    }
                                    finalResourceData = new Blob([finalResourceData], {type: textResourceContentType});
                                }
                                //noinspection JSUnresolvedVariable,JSUnresolvedFunction
                                var resourceObjectURL = window.URL.createObjectURL(finalResourceData);
                                _publicationResourcesCache.putResource(resourceUriRelativeToPackageDocument,
                                    resourceObjectURL, finalResourceData);
                                // TODO: take care of releasing object URLs when no longer needed
                                replaceRefAttrInElem(resourceObjectURL);
                                resolutionDeferred.resolve();
                            };

                            if (resourceDataPreprocessing) {
                                resourceDataPreprocessing(resourceData, resourceUriRelativeToPackageDocument,
                                    replaceResourceURL);
                            } else {
                                replaceResourceURL(resourceData);
                            }
                        }, onerror);
                }
            }

            function fetchResourceForCssUrlMatch(cssUrlMatch, cssResourceDownloadDeferreds,
                                                 styleSheetUriRelativeToPackageDocument, stylesheetCssResourceUrlsMap,
                                                 isStyleSheetResource) {
                var origMatchedUrlString = cssUrlMatch[0];

                var extractedUrlCandidates = cssUrlMatch.slice(2);
                var extractedUrl = _.find(extractedUrlCandidates, function(matchGroup){ return typeof matchGroup !== 'undefined' });

                var extractedUri = new URI(extractedUrl);
                var isCssUrlRelative = extractedUri.scheme() === '';
                if (!isCssUrlRelative) {
                    // Absolute URLs don't need programmatic fetching
                    return;
                }
                var resourceUriRelativeToPackageDocument = (new URI(extractedUrl)).absoluteTo(styleSheetUriRelativeToPackageDocument).toString();

                var cachedResourceURL = _publicationResourcesCache.getResourceURL(resourceUriRelativeToPackageDocument);

                if (cachedResourceURL) {
                    stylesheetCssResourceUrlsMap[origMatchedUrlString] = {
                        isStyleSheetResource: isStyleSheetResource,
                        resourceObjectURL: cachedResourceURL
                    };
                } else {
                    var cssUrlFetchDeferred = $.Deferred();
                    cssResourceDownloadDeferreds.push(cssUrlFetchDeferred);

                    var processedBlobCallback = function (resourceDataBlob) {
                        //noinspection JSUnresolvedVariable,JSUnresolvedFunction
                        var resourceObjectURL = window.URL.createObjectURL(resourceDataBlob);
                        stylesheetCssResourceUrlsMap[origMatchedUrlString] = {
                            isStyleSheetResource: isStyleSheetResource,
                            resourceObjectURL: resourceObjectURL
                        };
                        _publicationResourcesCache.putResource(resourceUriRelativeToPackageDocument,
                            resourceObjectURL, resourceDataBlob);
                        cssUrlFetchDeferred.resolve();
                    };
                    var fetchErrorCallback = function (error) {
                        _handleError(error);
                        cssUrlFetchDeferred.resolve();
                    };

                    var fetchMode;
                    var fetchCallback;
                    if (isStyleSheetResource) {
                        // TODO: test whether recursion works for nested @import rules with arbitrary indirection depth.
                        fetchMode = 'text';
                        fetchCallback = function (styleSheetResourceData) {
                            preprocessCssStyleSheetData(styleSheetResourceData, resourceUriRelativeToPackageDocument,
                                function (preprocessedStyleSheetData) {
                                    var resourceDataBlob = new Blob([preprocessedStyleSheetData], {type: 'text/css'});
                                    processedBlobCallback(resourceDataBlob);
                                })
                        }
                    } else {
                        fetchMode = 'blob';
                        fetchCallback = processedBlobCallback;
                    }

                    _publicationFetcher.relativeToPackageFetchFileContents(resourceUriRelativeToPackageDocument,
                        fetchMode,
                        fetchCallback, fetchErrorCallback);
                }
            }

            function preprocessCssStyleSheetData(styleSheetResourceData, styleSheetUriRelativeToPackageDocument,
                                                 callback) {
                var cssUrlRegexp = /[Uu][Rr][Ll]\(\s*([']([^']+)[']|["]([^"]+)["]|([^)]+))\s*\)/g;
                var nonUrlCssImportRegexp = /@[Ii][Mm][Pp][Oo][Rr][Tt]\s*('([^']+)'|"([^"]+)")/g;
                var stylesheetCssResourceUrlsMap = {};
                var cssResourceDownloadDeferreds = [];
                // Go through the stylesheet text using all regexps and process according to those regexp matches, if any:
                [nonUrlCssImportRegexp, cssUrlRegexp].forEach(function (processingRegexp) {
                    // extract all URL references in the CSS sheet,
                    var cssUrlMatch = processingRegexp.exec(styleSheetResourceData);
                    while (cssUrlMatch != null) {
                        // then fetch and replace them with corresponding object URLs:
                        var isStyleSheetResource = false;
                        // Special handling of @import-ed stylesheet files - recursive preprocessing:
                        // TODO: will not properly handle @import url(...):
                        if (processingRegexp == nonUrlCssImportRegexp) {
                            // This resource URL points to an @import-ed CSS stylesheet file. Need to preprocess its text
                            // after fetching but before making an object URL of it:
                            isStyleSheetResource = true;
                        }
                        fetchResourceForCssUrlMatch(cssUrlMatch, cssResourceDownloadDeferreds,
                            styleSheetUriRelativeToPackageDocument, stylesheetCssResourceUrlsMap, isStyleSheetResource);
                        cssUrlMatch = processingRegexp.exec(styleSheetResourceData);
                    }

                });

                if (cssResourceDownloadDeferreds.length > 0) {
                    $.when.apply($, cssResourceDownloadDeferreds).done(function () {
                        for (var origMatchedUrlString in stylesheetCssResourceUrlsMap) {
                            var processedResourceDescriptor = stylesheetCssResourceUrlsMap[origMatchedUrlString];

                            var processedUrlString;
                            if (processedResourceDescriptor.isStyleSheetResource) {
                                processedUrlString = '@import "' + processedResourceDescriptor.resourceObjectURL + '"';
                            } else {
                                processedUrlString = "url('" + processedResourceDescriptor.resourceObjectURL + "')";
                            }
                            var origMatchedUrlStringEscaped = origMatchedUrlString.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
                                "\\$&");
                            var origMatchedUrlStringRegExp = new RegExp(origMatchedUrlStringEscaped, 'g');
                            //noinspection JSCheckFunctionSignatures
                            styleSheetResourceData =
                                styleSheetResourceData.replace(origMatchedUrlStringRegExp, processedUrlString, 'g');

                        }
                        callback(styleSheetResourceData);
                    });
                } else {
                    callback(styleSheetResourceData);
                }
            }

            function resolveResourceElements(elemName, refAttr, fetchMode, resolutionDeferreds, onerror,
                                             resourceDataPreprocessing) {

                var resolvedElems = $(elemName + '[' + refAttr + ']', _contentDocumentDom);

                resolvedElems.each(function (index, resolvedElem) {
                    var refAttrOrigVal = $(resolvedElem).attr(refAttr);
                    var refAttrUri = new URI(refAttrOrigVal);

                    if (refAttrUri.scheme() === '') {
                        // Relative URI, fetch from packed EPUB archive:

                        fetchResourceForElement(resolvedElem, refAttrOrigVal, refAttr, fetchMode, resolutionDeferreds,
                            onerror, resourceDataPreprocessing);
                    }
                });
            }

            function resolveDocumentImages(resolutionDeferreds, onerror) {
                resolveResourceElements('img', 'src', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentAudios(resolutionDeferreds, onerror) {
                resolveResourceElements('audio', 'src', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentVideos(resolutionDeferreds, onerror) {
                resolveResourceElements('video', 'src', 'blob', resolutionDeferreds, onerror);
                resolveResourceElements('video', 'poster', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentScripts(resolutionDeferreds, onerror) {
                resolveResourceElements('script', 'src', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentIframes(resolutionDeferreds, onerror) {
                resolveResourceElements('iframe', 'src', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentLinkStylesheets(resolutionDeferreds, onerror) {
                resolveResourceElements('link', 'href', 'text', resolutionDeferreds, onerror,
                    preprocessCssStyleSheetData);
            }

            function resolveDocumentEmbeddedStylesheets(resolutionDeferreds, onerror) {
                var resolvedElems = $('style', _contentDocumentDom);
                resolvedElems.each(function (index, resolvedElem) {
                    var resolutionDeferred = $.Deferred();
                    resolutionDeferreds.push(resolutionDeferred);
                    var styleSheetData = $(resolvedElem).text();
                    preprocessCssStyleSheetData(styleSheetData, _contentDocumentPathRelativeToPackage,
                        function (resolvedStylesheetData) {
                            $(resolvedElem).text(resolvedStylesheetData);
                            resolutionDeferred.resolve();
                        });
                });
            }

        };

        return ContentDocumentFetcher;

    }
);
<commit_msg>Replace image href in SVG element with blob url<commit_after>//  Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification,
//  are permitted provided that the following conditions are met:
//  1. Redistributions of source code must retain the above copyright notice, this
//  list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice,
//  this list of conditions and the following disclaimer in the documentation and/or
//  other materials provided with the distribution.
//  3. Neither the name of the organization nor the names of its contributors may be
//  used to endorse or promote products derived from this software without specific
//  prior written permission.

define(
    ['require', 'module', 'jquery', 'underscore', 'URIjs', './discover_content_type'],
    function (require, module, $, _, URI, ContentTypeDiscovery) {

        var ContentDocumentFetcher = function (publicationFetcher, spineItem, loadedDocumentUri, publicationResourcesCache) {

            var self = this;

            var _contentDocumentPathRelativeToPackage = spineItem.href;
            var _publicationFetcher = publicationFetcher;
            var _contentDocumentText;
            var _srcMediaType = spineItem.media_type;
            var _contentDocumentDom;
            var _publicationResourcesCache = publicationResourcesCache;

            // PUBLIC API

            this.fetchContentDocumentAndResolveDom = function (contentDocumentResolvedCallback, errorCallback) {
                _publicationFetcher.relativeToPackageFetchFileContents(_contentDocumentPathRelativeToPackage, 'text',
                    function (contentDocumentText) {
                        _contentDocumentText = contentDocumentText;
                        self.resolveInternalPackageResources(contentDocumentResolvedCallback, errorCallback);
                    }, errorCallback
                );
            };

            this.resolveInternalPackageResources = function (resolvedDocumentCallback, onerror) {

                _contentDocumentDom = _publicationFetcher.markupParser.parseMarkup(_contentDocumentText, _srcMediaType);
                setBaseUri(_contentDocumentDom, loadedDocumentUri);

                var resolutionDeferreds = [];

                if (_publicationFetcher.shouldFetchMediaAssetsProgrammatically()) {
                    resolveDocumentImages(resolutionDeferreds, onerror);
                    resolveDocumentAudios(resolutionDeferreds, onerror);
                    resolveDocumentVideos(resolutionDeferreds, onerror);
                }
                // TODO: recursive fetching, parsing and DOM construction of documents in IFRAMEs,
                // with CSS preprocessing and obfuscated font handling
                resolveDocumentIframes(resolutionDeferreds, onerror);
                // TODO: resolution (e.g. using DOM mutation events) of scripts loaded dynamically by scripts
                resolveDocumentScripts(resolutionDeferreds, onerror);
                resolveDocumentLinkStylesheets(resolutionDeferreds, onerror);
                resolveDocumentEmbeddedStylesheets(resolutionDeferreds, onerror);

                $.when.apply($, resolutionDeferreds).done(function () {
                    resolvedDocumentCallback(_contentDocumentDom);
                });

            };

            // INTERNAL FUNCTIONS

            function setBaseUri(documentDom, baseURI) {
                var baseElem = documentDom.getElementsByTagName('base')[0];
                if (!baseElem) {
                    baseElem = documentDom.createElement('base');

                    var anchor = documentDom.getElementsByTagName('head')[0];
                    anchor.insertBefore(baseElem, anchor.childNodes[0]);
                }
                baseElem.setAttribute('href', baseURI);
            }

            function _handleError(err) {
                if (err) {
                    if (err.message) {
                        console.error(err.message);
                    }
                    if (err.stack) {
                        console.error(err.stack);
                    }
                }
                console.error(err);
            }

            function fetchResourceForElement(resolvedElem, refAttrOrigVal, refAttr, fetchMode, resolutionDeferreds,
                                             onerror, resourceDataPreprocessing) {
                var resourceUriRelativeToPackageDocument = (new URI(refAttrOrigVal)).absoluteTo(_contentDocumentPathRelativeToPackage).toString();

                var cachedResourceUrl = _publicationResourcesCache.getResourceURL(resourceUriRelativeToPackageDocument);

                function replaceRefAttrInElem(newResourceUrl) {
                    // Store original refAttrVal in a special attribute to provide access to the original href:
                    $(resolvedElem).data('epubZipOrigHref', refAttrOrigVal);
                    $(resolvedElem).attr(refAttr, newResourceUrl);
                }

                if (cachedResourceUrl) {
                    replaceRefAttrInElem(cachedResourceUrl);
                } else {
                    var resolutionDeferred = $.Deferred();
                    resolutionDeferreds.push(resolutionDeferred);

                    _publicationFetcher.relativeToPackageFetchFileContents(resourceUriRelativeToPackageDocument,
                        fetchMode,
                        function (resourceData) {

                            // Generate a function to replace element's resource URL with URL of fetched data.
                            // The function will either be called directly, immediately (if no preprocessing of resourceData is in effect)
                            // or indirectly, later after resourceData preprocessing finishes:
                            var replaceResourceURL = function (finalResourceData) {
                                // Creating an object URL requires a Blob object, so resource data fetched in text mode needs to be wrapped in a Blob:
                                if (fetchMode === 'text') {
                                    var textResourceContentType = ContentTypeDiscovery.identifyContentTypeFromFileName(resourceUriRelativeToPackageDocument);
                                    var declaredType = $(resolvedElem).attr('type');
                                    if (declaredType) {
                                        textResourceContentType = declaredType;
                                    }
                                    finalResourceData = new Blob([finalResourceData], {type: textResourceContentType});
                                }
                                //noinspection JSUnresolvedVariable,JSUnresolvedFunction
                                var resourceObjectURL = window.URL.createObjectURL(finalResourceData);
                                _publicationResourcesCache.putResource(resourceUriRelativeToPackageDocument,
                                    resourceObjectURL, finalResourceData);
                                // TODO: take care of releasing object URLs when no longer needed
                                replaceRefAttrInElem(resourceObjectURL);
                                resolutionDeferred.resolve();
                            };

                            if (resourceDataPreprocessing) {
                                resourceDataPreprocessing(resourceData, resourceUriRelativeToPackageDocument,
                                    replaceResourceURL);
                            } else {
                                replaceResourceURL(resourceData);
                            }
                        }, onerror);
                }
            }

            function fetchResourceForCssUrlMatch(cssUrlMatch, cssResourceDownloadDeferreds,
                                                 styleSheetUriRelativeToPackageDocument, stylesheetCssResourceUrlsMap,
                                                 isStyleSheetResource) {
                var origMatchedUrlString = cssUrlMatch[0];

                var extractedUrlCandidates = cssUrlMatch.slice(2);
                var extractedUrl = _.find(extractedUrlCandidates, function(matchGroup){ return typeof matchGroup !== 'undefined' });

                var extractedUri = new URI(extractedUrl);
                var isCssUrlRelative = extractedUri.scheme() === '';
                if (!isCssUrlRelative) {
                    // Absolute URLs don't need programmatic fetching
                    return;
                }
                var resourceUriRelativeToPackageDocument = (new URI(extractedUrl)).absoluteTo(styleSheetUriRelativeToPackageDocument).toString();

                var cachedResourceURL = _publicationResourcesCache.getResourceURL(resourceUriRelativeToPackageDocument);

                if (cachedResourceURL) {
                    stylesheetCssResourceUrlsMap[origMatchedUrlString] = {
                        isStyleSheetResource: isStyleSheetResource,
                        resourceObjectURL: cachedResourceURL
                    };
                } else {
                    var cssUrlFetchDeferred = $.Deferred();
                    cssResourceDownloadDeferreds.push(cssUrlFetchDeferred);

                    var processedBlobCallback = function (resourceDataBlob) {
                        //noinspection JSUnresolvedVariable,JSUnresolvedFunction
                        var resourceObjectURL = window.URL.createObjectURL(resourceDataBlob);
                        stylesheetCssResourceUrlsMap[origMatchedUrlString] = {
                            isStyleSheetResource: isStyleSheetResource,
                            resourceObjectURL: resourceObjectURL
                        };
                        _publicationResourcesCache.putResource(resourceUriRelativeToPackageDocument,
                            resourceObjectURL, resourceDataBlob);
                        cssUrlFetchDeferred.resolve();
                    };
                    var fetchErrorCallback = function (error) {
                        _handleError(error);
                        cssUrlFetchDeferred.resolve();
                    };

                    var fetchMode;
                    var fetchCallback;
                    if (isStyleSheetResource) {
                        // TODO: test whether recursion works for nested @import rules with arbitrary indirection depth.
                        fetchMode = 'text';
                        fetchCallback = function (styleSheetResourceData) {
                            preprocessCssStyleSheetData(styleSheetResourceData, resourceUriRelativeToPackageDocument,
                                function (preprocessedStyleSheetData) {
                                    var resourceDataBlob = new Blob([preprocessedStyleSheetData], {type: 'text/css'});
                                    processedBlobCallback(resourceDataBlob);
                                })
                        }
                    } else {
                        fetchMode = 'blob';
                        fetchCallback = processedBlobCallback;
                    }

                    _publicationFetcher.relativeToPackageFetchFileContents(resourceUriRelativeToPackageDocument,
                        fetchMode,
                        fetchCallback, fetchErrorCallback);
                }
            }

            function preprocessCssStyleSheetData(styleSheetResourceData, styleSheetUriRelativeToPackageDocument,
                                                 callback) {
                var cssUrlRegexp = /[Uu][Rr][Ll]\(\s*([']([^']+)[']|["]([^"]+)["]|([^)]+))\s*\)/g;
                var nonUrlCssImportRegexp = /@[Ii][Mm][Pp][Oo][Rr][Tt]\s*('([^']+)'|"([^"]+)")/g;
                var stylesheetCssResourceUrlsMap = {};
                var cssResourceDownloadDeferreds = [];
                // Go through the stylesheet text using all regexps and process according to those regexp matches, if any:
                [nonUrlCssImportRegexp, cssUrlRegexp].forEach(function (processingRegexp) {
                    // extract all URL references in the CSS sheet,
                    var cssUrlMatch = processingRegexp.exec(styleSheetResourceData);
                    while (cssUrlMatch != null) {
                        // then fetch and replace them with corresponding object URLs:
                        var isStyleSheetResource = false;
                        // Special handling of @import-ed stylesheet files - recursive preprocessing:
                        // TODO: will not properly handle @import url(...):
                        if (processingRegexp == nonUrlCssImportRegexp) {
                            // This resource URL points to an @import-ed CSS stylesheet file. Need to preprocess its text
                            // after fetching but before making an object URL of it:
                            isStyleSheetResource = true;
                        }
                        fetchResourceForCssUrlMatch(cssUrlMatch, cssResourceDownloadDeferreds,
                            styleSheetUriRelativeToPackageDocument, stylesheetCssResourceUrlsMap, isStyleSheetResource);
                        cssUrlMatch = processingRegexp.exec(styleSheetResourceData);
                    }

                });

                if (cssResourceDownloadDeferreds.length > 0) {
                    $.when.apply($, cssResourceDownloadDeferreds).done(function () {
                        for (var origMatchedUrlString in stylesheetCssResourceUrlsMap) {
                            var processedResourceDescriptor = stylesheetCssResourceUrlsMap[origMatchedUrlString];

                            var processedUrlString;
                            if (processedResourceDescriptor.isStyleSheetResource) {
                                processedUrlString = '@import "' + processedResourceDescriptor.resourceObjectURL + '"';
                            } else {
                                processedUrlString = "url('" + processedResourceDescriptor.resourceObjectURL + "')";
                            }
                            var origMatchedUrlStringEscaped = origMatchedUrlString.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
                                "\\$&");
                            var origMatchedUrlStringRegExp = new RegExp(origMatchedUrlStringEscaped, 'g');
                            //noinspection JSCheckFunctionSignatures
                            styleSheetResourceData =
                                styleSheetResourceData.replace(origMatchedUrlStringRegExp, processedUrlString, 'g');

                        }
                        callback(styleSheetResourceData);
                    });
                } else {
                    callback(styleSheetResourceData);
                }
            }

            function resolveResourceElements(elemName, refAttr, fetchMode, resolutionDeferreds, onerror,
                                             resourceDataPreprocessing) {

                var resolvedElems = $(elemName + '[' + refAttr + ']', _contentDocumentDom);
                refAttr = refAttr.replace(/\\/g, '');

                resolvedElems.each(function (index, resolvedElem) {
                    var refAttrOrigVal = $(resolvedElem).attr(refAttr);
                    var refAttrUri = new URI(refAttrOrigVal);

                    if (refAttrUri.scheme() === '') {
                        // Relative URI, fetch from packed EPUB archive:

                        fetchResourceForElement(resolvedElem, refAttrOrigVal, refAttr, fetchMode, resolutionDeferreds,
                            onerror, resourceDataPreprocessing);
                    }
                });
            }

            function resolveDocumentImages(resolutionDeferreds, onerror) {
                resolveResourceElements('img', 'src', 'blob', resolutionDeferreds, onerror);
                resolveResourceElements('image', 'xlink\\:href', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentAudios(resolutionDeferreds, onerror) {
                resolveResourceElements('audio', 'src', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentVideos(resolutionDeferreds, onerror) {
                resolveResourceElements('video', 'src', 'blob', resolutionDeferreds, onerror);
                resolveResourceElements('video', 'poster', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentScripts(resolutionDeferreds, onerror) {
                resolveResourceElements('script', 'src', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentIframes(resolutionDeferreds, onerror) {
                resolveResourceElements('iframe', 'src', 'blob', resolutionDeferreds, onerror);
            }

            function resolveDocumentLinkStylesheets(resolutionDeferreds, onerror) {
                resolveResourceElements('link', 'href', 'text', resolutionDeferreds, onerror,
                    preprocessCssStyleSheetData);
            }

            function resolveDocumentEmbeddedStylesheets(resolutionDeferreds, onerror) {
                var resolvedElems = $('style', _contentDocumentDom);
                resolvedElems.each(function (index, resolvedElem) {
                    var resolutionDeferred = $.Deferred();
                    resolutionDeferreds.push(resolutionDeferred);
                    var styleSheetData = $(resolvedElem).text();
                    preprocessCssStyleSheetData(styleSheetData, _contentDocumentPathRelativeToPackage,
                        function (resolvedStylesheetData) {
                            $(resolvedElem).text(resolvedStylesheetData);
                            resolutionDeferred.resolve();
                        });
                });
            }

        };

        return ContentDocumentFetcher;

    }
);
<|endoftext|>


<commit_before>require("../env");
require("../../d3");

var vows = require("vows"),
    assert = require("assert");

var suite = vows.describe("d3.range");

suite.addBatch({
  "range": {
    topic: function() {
      return d3.range;
    },
    "start is an inclusive lower bound": function(range) {
      assert.equal(range(5)[0], 0);
      assert.equal(range(1, 5)[0], 1);
      assert.equal(range(5, 1, -1)[0], 5);
    },
    "stop is an exclusive upper bound": function(range) {
      assert.equal(range(5)[4], 4);
      assert.equal(range(1, 5)[3], 4);
      assert.equal(range(5, 1, -1)[3], 2);
    },
    "with one argument, returns integers [0 … stop)": function(range) {
      assert.deepEqual(range(0), []);
      assert.deepEqual(range(1), [0]);
      assert.deepEqual(range(5), [0, 1, 2, 3, 4]);
    },
    "with two arguments, returns integers [start … stop)": function(range) {
      assert.deepEqual(range(0, 5), [0, 1, 2, 3, 4]);
      assert.deepEqual(range(5, 9), [5, 6, 7, 8]);
    },
    "with three arguments, returns start + k * step": function(range) {
      assert.deepEqual(range(0, 5, 1), [0, 1, 2, 3, 4]);
      assert.deepEqual(range(5, 9, .5), [5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5]);
      assert.deepEqual(range(5, 8.5, .5), [5, 5.5, 6, 6.5, 7, 7.5, 8]);
      assert.deepEqual(range(2, 0, -.5), [2, 1.5, 1, .5]);
      assert.deepEqual(range(0,0.5,0.1), [0, 0.1, 0.2, 0.3, 0.4]);
	  assert.deepEqual(range(2.1e-31, 5e-31, 1.1e-31), [2.1e-31, 3.2e-31, 4.3e-31]);
    },
    "returns an ascending range if step is positive": function(range) {
      assert.deepEqual(range(0, 5, 1), [0, 1, 2, 3, 4]);
    },
    "returns a descending range if step is negative": function(range) {
      assert.deepEqual(range(5, 0, -1), [5, 4, 3, 2, 1]);
    },
    "returns an empty range if start, stop or step are NaN": function(range) {
      assert.isEmpty(range(0, NaN));
      assert.isEmpty(range(1, NaN));
      assert.isEmpty(range(-1, NaN));
      assert.isEmpty(range(0, undefined));
      assert.isEmpty(range(1, undefined));
      assert.isEmpty(range(-1, undefined));
      assert.isEmpty(range(NaN, 0));
      assert.isEmpty(range(NaN, 1));
      assert.isEmpty(range(NaN, -1));
      assert.isEmpty(range(undefined, 0));
      assert.isEmpty(range(undefined, 1));
      assert.isEmpty(range(undefined, -1));
      assert.isEmpty(range(NaN, NaN));
      assert.isEmpty(range(undefined, undefined));
      assert.isEmpty(range(NaN, NaN, NaN));
      assert.isEmpty(range(undefined, undefined, undefined));
      assert.isEmpty(range(0, 10, NaN));
      assert.isEmpty(range(10, 0, NaN));
      assert.isEmpty(range(0, 10, undefined));
      assert.isEmpty(range(10, 0, undefined));
    },
    "returns an empty range if start equals stop": function(range) {
      assert.isEmpty(range(10, 10));
      assert.isEmpty(range(10, 10, 1));
      assert.isEmpty(range(10, 10, -1));
      assert.isEmpty(range(10, 10, -.5));
      assert.isEmpty(range(10, 10, .5));
      assert.isEmpty(range(0, 0));
      assert.isEmpty(range(0, 0, 1));
      assert.isEmpty(range(0, 0, -1));
      assert.isEmpty(range(0, 0, -.5));
      assert.isEmpty(range(0, 0, .5));
    },
    "returns an empty range if stop is less than start and step is positive": function(range) {
      assert.isEmpty(range(20, 10));
      assert.isEmpty(range(20, 10, 2));
      assert.isEmpty(range(20, 10, 1));
      assert.isEmpty(range(20, 10, .5));
    },
    "returns an empty range if stop is greater than start and step is negative": function(range) {
      assert.isEmpty(range(10, 20, -2));
      assert.isEmpty(range(10, 20, -1));
      assert.isEmpty(range(10, 20, -.5));
    }
  }
});

suite.export(module);
<commit_msg>forgot to follow spacing rules<commit_after>require("../env");
require("../../d3");

var vows = require("vows"),
    assert = require("assert");

var suite = vows.describe("d3.range");

suite.addBatch({
  "range": {
    topic: function() {
      return d3.range;
    },
    "start is an inclusive lower bound": function(range) {
      assert.equal(range(5)[0], 0);
      assert.equal(range(1, 5)[0], 1);
      assert.equal(range(5, 1, -1)[0], 5);
    },
    "stop is an exclusive upper bound": function(range) {
      assert.equal(range(5)[4], 4);
      assert.equal(range(1, 5)[3], 4);
      assert.equal(range(5, 1, -1)[3], 2);
    },
    "with one argument, returns integers [0 … stop)": function(range) {
      assert.deepEqual(range(0), []);
      assert.deepEqual(range(1), [0]);
      assert.deepEqual(range(5), [0, 1, 2, 3, 4]);
    },
    "with two arguments, returns integers [start … stop)": function(range) {
      assert.deepEqual(range(0, 5), [0, 1, 2, 3, 4]);
      assert.deepEqual(range(5, 9), [5, 6, 7, 8]);
    },
    "with three arguments, returns start + k * step": function(range) {
      assert.deepEqual(range(0, 5, 1), [0, 1, 2, 3, 4]);
      assert.deepEqual(range(5, 9, .5), [5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5]);
      assert.deepEqual(range(5, 8.5, .5), [5, 5.5, 6, 6.5, 7, 7.5, 8]);
      assert.deepEqual(range(2, 0, -.5), [2, 1.5, 1, .5]);
      assert.deepEqual(range(0,0.5,0.1), [0, 0.1, 0.2, 0.3, 0.4]);
      assert.deepEqual(range(2.1e-31, 5e-31, 1.1e-31), [2.1e-31, 3.2e-31, 4.3e-31]);
    },
    "returns an ascending range if step is positive": function(range) {
      assert.deepEqual(range(0, 5, 1), [0, 1, 2, 3, 4]);
    },
    "returns a descending range if step is negative": function(range) {
      assert.deepEqual(range(5, 0, -1), [5, 4, 3, 2, 1]);
    },
    "returns an empty range if start, stop or step are NaN": function(range) {
      assert.isEmpty(range(0, NaN));
      assert.isEmpty(range(1, NaN));
      assert.isEmpty(range(-1, NaN));
      assert.isEmpty(range(0, undefined));
      assert.isEmpty(range(1, undefined));
      assert.isEmpty(range(-1, undefined));
      assert.isEmpty(range(NaN, 0));
      assert.isEmpty(range(NaN, 1));
      assert.isEmpty(range(NaN, -1));
      assert.isEmpty(range(undefined, 0));
      assert.isEmpty(range(undefined, 1));
      assert.isEmpty(range(undefined, -1));
      assert.isEmpty(range(NaN, NaN));
      assert.isEmpty(range(undefined, undefined));
      assert.isEmpty(range(NaN, NaN, NaN));
      assert.isEmpty(range(undefined, undefined, undefined));
      assert.isEmpty(range(0, 10, NaN));
      assert.isEmpty(range(10, 0, NaN));
      assert.isEmpty(range(0, 10, undefined));
      assert.isEmpty(range(10, 0, undefined));
    },
    "returns an empty range if start equals stop": function(range) {
      assert.isEmpty(range(10, 10));
      assert.isEmpty(range(10, 10, 1));
      assert.isEmpty(range(10, 10, -1));
      assert.isEmpty(range(10, 10, -.5));
      assert.isEmpty(range(10, 10, .5));
      assert.isEmpty(range(0, 0));
      assert.isEmpty(range(0, 0, 1));
      assert.isEmpty(range(0, 0, -1));
      assert.isEmpty(range(0, 0, -.5));
      assert.isEmpty(range(0, 0, .5));
    },
    "returns an empty range if stop is less than start and step is positive": function(range) {
      assert.isEmpty(range(20, 10));
      assert.isEmpty(range(20, 10, 2));
      assert.isEmpty(range(20, 10, 1));
      assert.isEmpty(range(20, 10, .5));
    },
    "returns an empty range if stop is greater than start and step is negative": function(range) {
      assert.isEmpty(range(10, 20, -2));
      assert.isEmpty(range(10, 20, -1));
      assert.isEmpty(range(10, 20, -.5));
    }
  }
});

suite.export(module);
<|endoftext|>


<commit_before>import Mousetrap from "mousetrap";
import throttle from "lodash.throttle";
import find from "lodash.find";

import Attractor from "./Attractor";
import Deflector from "./Deflector";
import Cannon from "./Cannon";
import Body from "./Body";

import * as canvasDraw from "./canvasDraw";
import * as constants from "./constants";
import * as pointUtils from "./pointUtils";

function Game() {
    this.canvas = document.getElementById("canvas");
    this.infoElement = document.getElementById("gen-info-text");

    this.canvas.style.position = "absolute";
    this.computeSize();

    // Get the drawing context.
    this.screen = this.canvas.getContext('2d');
    this.screen.font = "10pt Arial";

    canvasDraw.setScreen(this.screen);

    this.deadBodies = [];
    this.bodies = [];
    this.attractors = [];
    this.deflectors = [];
    this.cannons = [];

    this.attractors.push(new Attractor(this, {
        x: constants.toReal(this.size.x / 4), y: constants.toReal(this.size.y / 4) // left left top
    }));

    this.attractors.push(new Attractor(this, {
        x: constants.toReal(this.size.x / 4 * 3) , y: constants.toReal(this.size.y / 4) // left right top
    }));

    this.attractors.push(new Attractor(this, {
        x: constants.toReal(this.size.x / 4), y: constants.toReal(this.size.y / 4 * 3) // left left bottom
    }));

    this.attractors.push(new Attractor(this, {
        x: constants.toReal(this.size.x / 4 * 3), y: constants.toReal(this.size.y / 4 * 3) // right right bottom
    }));

    let spawnBody = (point, isManual = false) => {
        this.addBody(new Body(this, point, isManual));
    };

    let spawnCannon = (point) => {
        let comparePoint = pointUtils.fromReal(point);
        let toPoint = pointUtils.toReal({
            x: comparePoint.x === this.size.x ? 0 : comparePoint.x === 0 ? this.size.x : comparePoint.x,
            y: comparePoint.y === this.size.y ? 0 : comparePoint.y === 0 ? this.size.y : comparePoint.y
        });

        this.cannons.push(new Cannon(this, point, toPoint));
    };

    let getClickableObjects = () => this.attractors.concat(this.cannons, this.deflectors);
    let getClickedObject = point => {
        return find(getClickableObjects(), object => {
            let distance = pointUtils.distanceBetween(object.center, point);

            // User clicked within a 50px circle or the object's radius around the center of the object
            if (distance <= object.radius) {
                return object;
            }
        });
    };

    let selectedObject;
    let mousePos;

    let adjustObject = (object, adjustment) => {
        if (object.type === "attractor" || object.type === "deflector") {
            if (selectedObject.radius > constants.toReal(1)) {
                selectedObject.radius += constants.toReal(adjustment);
                selectedObject.calculateMass();
            }
        } else if (object.type === "cannon") {
            adjustment = -adjustment;
            if (selectedObject.rate > 1) {
                selectedObject.rate += adjustment; // The + should make the rate faster, - slower
            } else if (selectedObject.rate === 1 && adjustment > 0) {
                selectedObject.rate += adjustment;
            }
        }
    };

    Mousetrap.bind("m", () => {
        if (mousePos && selectedObject) {
            selectedObject.center = mousePos;
        }
    });

    Mousetrap.bind("k", () => {
        if (mousePos && selectedObject) {
            selectedObject.isAlive = false;
        }
    });

    Mousetrap.bind("+", () => {
        if (mousePos && selectedObject) {
            adjustObject(selectedObject, +1);
        }
    });

    Mousetrap.bind("-", () => {
        if (mousePos && selectedObject) {
            adjustObject(selectedObject, -1);
        }
    });

    Mousetrap.bind("t", () => {
        if (mousePos && selectedObject && selectedObject.type === "cannon") { // Selected object is a cannon
            selectedObject.select(mousePos);
        }
    });

    Mousetrap.bind("shift+b", () => {
        this.bodies = [];
    });

    Mousetrap.bind("shift+a", () => {
        this.attractors = [];
    });

    Mousetrap.bind("shift+d", () => {
        this.deflectors = [];
    });

    Mousetrap.bind("shift+c", () => {
        this.cannons = [];
    });

    let processSpawning = (type) => {
        if (mousePos) {
            if (type === "attractor") {
                this.attractors.push(new Attractor(this, mousePos));
            } else if (type === "deflector") {
                this.deflectors.push(new Deflector(this, mousePos));
            } else if (type === "cannon") {
                spawnCannon(mousePos);
            } else if (type === "body") {
                spawnBody(mousePos, true);
            }
        }
    };

    processSpawning = throttle(processSpawning, 50, { leading: true, trailing: false });

    Mousetrap.bind("a", () => {
        processSpawning("attractor");
    });

    Mousetrap.bind("d", () => {
        processSpawning("deflector");
    });

    Mousetrap.bind("c", () => {
        processSpawning("cannon");
    });

    Mousetrap.bind("b", () => {
        processSpawning("body");
    });

    this.canvas.addEventListener("mousedown", e => {
        let {clientX, clientY} = e;
        const point = pointUtils.toReal({ x: clientX, y: clientY });
        mousePos = point;

        let clickedObject = getClickedObject(point);

        getClickableObjects().forEach(object => object.selected = false);
        if (clickedObject) {
            clickedObject.selected = true;
        }

        selectedObject = clickedObject;
    });

    this.canvas.addEventListener("mousemove", e => {
        let {clientX, clientY} = e;
        const point = pointUtils.toReal({ x: clientX, y: clientY });
        mousePos = point;
    });

    let tick = () => {
        this.screen.clearRect(0, 0, this.size.x, this.size.y); //allows this.update() to draw vectors
        this.update();
        this.draw();
        requestAnimationFrame(tick);
    };

    this.updatedAt = Date.now();
    requestAnimationFrame(tick);
}

Game.prototype.setInfoText = function (text) {
    this.infoElement.innerHTML = text;
};

Game.prototype.pointInAttractor = function (point) {
    let distanceTo = attractor => Math.ceil(pointUtils.distanceBetween(point, attractor.center));

    let minumumData = null;

    this.attractors.forEach(attractor => {
        let distance = distanceTo(attractor);
        if (minumumData === null || distance < minumumData.distance) {
            minumumData = { distance, attractor };
        }
    });

    if (minumumData && minumumData.attractor.radius >= minumumData.distance) {
        return minumumData;
    }

    return null;
};

Game.prototype.computeSize = function() {
    this.canvas.width = document.body.clientWidth;
    this.canvas.height = document.body.clientHeight;

    this.size = { x: this.canvas.width, y: this.canvas.height };
    this.realSize = { x: constants.toReal(this.size.x), y: constants.toReal(this.size.y) };
};

Game.prototype.cannonNearPoint = function (point) {
    let distanceTo = (cannon) => Math.ceil(pointUtils.distanceBetween(point, cannon.center));

    let distances = this.cannons.map(cannon => distanceTo(cannon));

    let minumumDistance = Math.min.apply(Math, distances);

    let returnVal = null;
    this.cannons.some(cannon => {
        if (distanceTo(cannon) === minumumDistance) {
            returnVal = { cannon, distance: minumumDistance };
        }
    });

    return returnVal;
};

Game.prototype.update = function() {
    const now = Date.now();
    let timeSinceUpdate = (now - this.updatedAt) * constants.TimeScale;
    timeSinceUpdate = Math.min(500, timeSinceUpdate);
    this.updatedAt = now;

    this.attractors.forEach(attractor => attractor.update());
    this.attractors = this.attractors.filter(attractor => attractor.isAlive);

    this.deflectors.forEach(deflector => deflector.update());
    this.deflectors = this.deflectors.filter(attractor => attractor.isAlive);

    this.bodies.forEach(body => body.update(timeSinceUpdate)); // Update must run before cleanup of dead bodies
    this.bodies = this.bodies.filter(body => body.isAlive);

    this.cannons.forEach(cannon => cannon.update());
    this.cannons = this.cannons.filter(cannon => cannon.isAlive);
};

Game.prototype.draw = function() {
    this.bodies
        .forEach(canvasDraw.drawBody);

    this.attractors.forEach(canvasDraw.drawBody);
    this.deflectors.forEach(canvasDraw.drawBody);
    this.cannons.forEach(canvasDraw.drawBody);

    this.setInfoText([
        `Bodies: ${this.bodies.length}`,
        `Cannons: ${this.cannons.length}`,
        `Attractors: ${this.attractors.length}`,
        `Deflectors: ${this.deflectors.length}`].join("
"));
};

Game.prototype.addBody = function(body) {
    this.bodies.push(body);
};

window.addEventListener("DOMContentLoaded", () => {
    window.game = new Game();

    let openButton = document.querySelector("#open-button");
    let closeButton = document.querySelector("#close-button");
    let modal = document.querySelector("#directions");

    openButton.addEventListener("click", () => {
        if (modal.style.display !== "table") modal.style.display = "table";
        else modal.style.display = "none";
    });
    closeButton.addEventListener("click", () => modal.style.display = "none");

});
window.addEventListener("resize", () => { window.game.computeSize(); });
<commit_msg>Remove initial 4 attractors.<commit_after>import Mousetrap from "mousetrap";
import throttle from "lodash.throttle";
import find from "lodash.find";

import Attractor from "./Attractor";
import Deflector from "./Deflector";
import Cannon from "./Cannon";
import Body from "./Body";

import * as canvasDraw from "./canvasDraw";
import * as constants from "./constants";
import * as pointUtils from "./pointUtils";

function Game() {
    this.canvas = document.getElementById("canvas");
    this.infoElement = document.getElementById("gen-info-text");

    this.canvas.style.position = "absolute";
    this.computeSize();

    // Get the drawing context.
    this.screen = this.canvas.getContext('2d');
    this.screen.font = "10pt Arial";

    canvasDraw.setScreen(this.screen);

    this.deadBodies = [];
    this.bodies = [];
    this.attractors = [];
    this.deflectors = [];
    this.cannons = [];

    let spawnBody = (point, isManual = false) => {
        this.addBody(new Body(this, point, isManual));
    };

    let spawnCannon = (point) => {
        let comparePoint = pointUtils.fromReal(point);
        let toPoint = pointUtils.toReal({
            x: comparePoint.x === this.size.x ? 0 : comparePoint.x === 0 ? this.size.x : comparePoint.x,
            y: comparePoint.y === this.size.y ? 0 : comparePoint.y === 0 ? this.size.y : comparePoint.y
        });

        this.cannons.push(new Cannon(this, point, toPoint));
    };

    let getClickableObjects = () => this.attractors.concat(this.cannons, this.deflectors);
    let getClickedObject = point => {
        return find(getClickableObjects(), object => {
            let distance = pointUtils.distanceBetween(object.center, point);

            // User clicked within a 50px circle or the object's radius around the center of the object
            if (distance <= object.radius) {
                return object;
            }
        });
    };

    let selectedObject;
    let mousePos;

    let adjustObject = (object, adjustment) => {
        if (object.type === "attractor" || object.type === "deflector") {
            if (selectedObject.radius > constants.toReal(1)) {
                selectedObject.radius += constants.toReal(adjustment);
                selectedObject.calculateMass();
            }
        } else if (object.type === "cannon") {
            adjustment = -adjustment;
            if (selectedObject.rate > 1) {
                selectedObject.rate += adjustment; // The + should make the rate faster, - slower
            } else if (selectedObject.rate === 1 && adjustment > 0) {
                selectedObject.rate += adjustment;
            }
        }
    };

    Mousetrap.bind("m", () => {
        if (mousePos && selectedObject) {
            selectedObject.center = mousePos;
        }
    });

    Mousetrap.bind("k", () => {
        if (mousePos && selectedObject) {
            selectedObject.isAlive = false;
        }
    });

    Mousetrap.bind("+", () => {
        if (mousePos && selectedObject) {
            adjustObject(selectedObject, +1);
        }
    });

    Mousetrap.bind("-", () => {
        if (mousePos && selectedObject) {
            adjustObject(selectedObject, -1);
        }
    });

    Mousetrap.bind("t", () => {
        if (mousePos && selectedObject && selectedObject.type === "cannon") { // Selected object is a cannon
            selectedObject.select(mousePos);
        }
    });

    Mousetrap.bind("shift+b", () => {
        this.bodies = [];
    });

    Mousetrap.bind("shift+a", () => {
        this.attractors = [];
    });

    Mousetrap.bind("shift+d", () => {
        this.deflectors = [];
    });

    Mousetrap.bind("shift+c", () => {
        this.cannons = [];
    });

    let processSpawning = (type) => {
        if (mousePos) {
            if (type === "attractor") {
                this.attractors.push(new Attractor(this, mousePos));
            } else if (type === "deflector") {
                this.deflectors.push(new Deflector(this, mousePos));
            } else if (type === "cannon") {
                spawnCannon(mousePos);
            } else if (type === "body") {
                spawnBody(mousePos, true);
            }
        }
    };

    processSpawning = throttle(processSpawning, 50, { leading: true, trailing: false });

    Mousetrap.bind("a", () => {
        processSpawning("attractor");
    });

    Mousetrap.bind("d", () => {
        processSpawning("deflector");
    });

    Mousetrap.bind("c", () => {
        processSpawning("cannon");
    });

    Mousetrap.bind("b", () => {
        processSpawning("body");
    });

    this.canvas.addEventListener("mousedown", e => {
        let {clientX, clientY} = e;
        const point = pointUtils.toReal({ x: clientX, y: clientY });
        mousePos = point;

        let clickedObject = getClickedObject(point);

        getClickableObjects().forEach(object => object.selected = false);
        if (clickedObject) {
            clickedObject.selected = true;
        }

        selectedObject = clickedObject;
    });

    this.canvas.addEventListener("mousemove", e => {
        let {clientX, clientY} = e;
        const point = pointUtils.toReal({ x: clientX, y: clientY });
        mousePos = point;
    });

    let tick = () => {
        this.screen.clearRect(0, 0, this.size.x, this.size.y); //allows this.update() to draw vectors
        this.update();
        this.draw();
        requestAnimationFrame(tick);
    };

    this.updatedAt = Date.now();
    requestAnimationFrame(tick);
}

Game.prototype.setInfoText = function (text) {
    this.infoElement.innerHTML = text;
};

Game.prototype.pointInAttractor = function (point) {
    let distanceTo = attractor => Math.ceil(pointUtils.distanceBetween(point, attractor.center));

    let minumumData = null;

    this.attractors.forEach(attractor => {
        let distance = distanceTo(attractor);
        if (minumumData === null || distance < minumumData.distance) {
            minumumData = { distance, attractor };
        }
    });

    if (minumumData && minumumData.attractor.radius >= minumumData.distance) {
        return minumumData;
    }

    return null;
};

Game.prototype.computeSize = function() {
    this.canvas.width = document.body.clientWidth;
    this.canvas.height = document.body.clientHeight;

    this.size = { x: this.canvas.width, y: this.canvas.height };
    this.realSize = { x: constants.toReal(this.size.x), y: constants.toReal(this.size.y) };
};

Game.prototype.cannonNearPoint = function (point) {
    let distanceTo = (cannon) => Math.ceil(pointUtils.distanceBetween(point, cannon.center));

    let distances = this.cannons.map(cannon => distanceTo(cannon));

    let minumumDistance = Math.min.apply(Math, distances);

    let returnVal = null;
    this.cannons.some(cannon => {
        if (distanceTo(cannon) === minumumDistance) {
            returnVal = { cannon, distance: minumumDistance };
        }
    });

    return returnVal;
};

Game.prototype.update = function() {
    const now = Date.now();
    let timeSinceUpdate = (now - this.updatedAt) * constants.TimeScale;
    timeSinceUpdate = Math.min(500, timeSinceUpdate);
    this.updatedAt = now;

    this.attractors.forEach(attractor => attractor.update());
    this.attractors = this.attractors.filter(attractor => attractor.isAlive);

    this.deflectors.forEach(deflector => deflector.update());
    this.deflectors = this.deflectors.filter(attractor => attractor.isAlive);

    this.bodies.forEach(body => body.update(timeSinceUpdate)); // Update must run before cleanup of dead bodies
    this.bodies = this.bodies.filter(body => body.isAlive);

    this.cannons.forEach(cannon => cannon.update());
    this.cannons = this.cannons.filter(cannon => cannon.isAlive);
};

Game.prototype.draw = function() {
    this.bodies
        .forEach(canvasDraw.drawBody);

    this.attractors.forEach(canvasDraw.drawBody);
    this.deflectors.forEach(canvasDraw.drawBody);
    this.cannons.forEach(canvasDraw.drawBody);

    this.setInfoText([
        `Bodies: ${this.bodies.length}`,
        `Cannons: ${this.cannons.length}`,
        `Attractors: ${this.attractors.length}`,
        `Deflectors: ${this.deflectors.length}`].join("
"));
};

Game.prototype.addBody = function(body) {
    this.bodies.push(body);
};

window.addEventListener("DOMContentLoaded", () => {
    window.game = new Game();

    let openButton = document.querySelector("#open-button");
    let closeButton = document.querySelector("#close-button");
    let modal = document.querySelector("#directions");

    openButton.addEventListener("click", () => {
        if (modal.style.display !== "table") modal.style.display = "table";
        else modal.style.display = "none";
    });
    closeButton.addEventListener("click", () => modal.style.display = "none");

});
window.addEventListener("resize", () => { window.game.computeSize(); });
<|endoftext|>