<commit_before>/*
 * Copyright (c) 2017, Hugo Freire <hugo@exec.sh>.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

const _ = require('lodash')
const Promise = require('bluebird')

const SQLite = require('./sqlite')

const uuidV4 = require('uuid/v4')

const transformRowToObject = function (row) {
  if (!row) {
    return
  }

  if (row.created_date) {
    row.created_date = new Date(row.created_date)
  }

  if (row.updated_date) {
    row.updated_date = new Date(row.updated_date)
  }

  if (row.liked_date) {
    row.liked_date = new Date(row.liked_date)
  }

  if (row.matched_date) {
    row.matched_date = new Date(row.matched_date)
  }

  if (row.trained_date) {
    row.trained_date = new Date(row.trained_date)
  }

  if (row.last_checked_out_date) {
    row.last_checked_out_date = new Date(row.last_checked_out_date)
  }

  if (row.data) {
    row.data = JSON.parse(row.data)
  }

  return row
}

const transformObjectToRow = function (object) {
  if (!object) {
    return
  }

  if (object.created_date instanceof Date) {
    object.created_date = object.created_date.toISOString()
  }

  if (object.updated_date instanceof Date) {
    object.updated_date = object.updated_date.toISOString()
  }

  if (object.matched_date instanceof Date) {
    object.matched_date = object.matched_date.toISOString()
  }

  if (object.liked_date instanceof Date) {
    object.liked_date = object.liked_date.toISOString()
  }

  if (object.trained_date instanceof Date) {
    object.trained_date = object.trained_date.toISOString()
  }

  if (object.last_checked_out_date instanceof Date) {
    object.last_checked_out_date = object.last_checked_out_date.toISOString()
  }

  if (object.data) {
    object.data = JSON.stringify(object.data)
  }

  return object
}

const queryAll = function (...args) {
  return SQLite.all(...args)
    .mapSeries((row) => transformRowToObject(row))
}

const buildWhereClause = (keys, values) => {
  if (_.includes(keys, 'liked_date')) {
    const index = _.indexOf(keys, 'liked_date')
    values[ index ] = values[ index ].split(' ')[ 0 ]
    values.splice(index, 0, values[ index ])
  }

  if (_.includes(keys, 'matched_date')) {
    const index = _.indexOf(keys, 'matched_date')
    values[ index ] = values[ index ].split(' ')[ 0 ]
    values.splice(index, 0, values[ index ])
  }

  if (_.includes(keys, 'last_checked_out_date')) {
    const index = _.indexOf(keys, 'last_checked_out_date')
    values[ index ] = values[ index ].split(' ')[ 0 ]
    values.splice(index, 0, values[ index ])
  }

  return keys.map((key) => {
    if (key === 'liked_date') {
      return `${key} > ? AND ${key} < strftime('%Y-%m-%dT%H:%M:%fZ', datetime(?, '+1 day'))`
    }

    if (key === 'matched_date') {
      return `${key} > ? AND ${key} < strftime('%Y-%m-%dT%H:%M:%fZ', datetime(?, '+1 day'))`
    }

    if (key === 'trained_date') {
      return `${key} < date(?, '+1 day')`
    }

    if (key === 'last_checked_out_date') {
      return `${key} > ? AND ${key} < strftime('%Y-%m-%dT%H:%M:%fZ', datetime(?, '+1 day'))`
    }

    return `${key} = ?`
  })
    .toString()
    .replace(/,/g, ' AND ')
    .replace(/datetime\(\? AND/, 'datetime(?,')
    // eslint-disable-next-line no-regex-spaces
    .replace(/AND  datetime/, ', datetime')
}

const buildSelectClause = (unique, select) => {
  if (unique && _.includes(select, 'liked_date')) {
    const index = _.indexOf(select, 'liked_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS liked_date`
  }

  if (unique && _.includes(select, 'matched_date')) {
    const index = _.indexOf(select, 'matched_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS matched_date`
  }

  if (unique && _.includes(select, 'trained_date')) {
    const index = _.indexOf(select, 'trained_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS trained_date`
  }

  if (unique && _.includes(select, 'last_checked_out_date')) {
    const index = _.indexOf(select, 'last_checked_out_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS last_checked_out_date`
  }

  return select
}

class Recommendations {
  save (channel, channelId, data) {
    const _data = transformObjectToRow(_.clone(data))

    const keys = _.keys(_data)
    const values = _.values(_data)

    return Promise.resolve()
      .then(() => this.findByChannelAndChannelId(channel, channelId))
      .then((person) => {
        if (person) {
          if (_.includes(keys, 'updated_date')) {
            const index = _.indexOf(keys, 'updated_date')
            values[ index ] = new Date().toISOString()
          } else {
            keys.push('updated_date')
            values.push(new Date().toISOString())
          }

          return SQLite.run(`UPDATE recommendations SET ${keys.map((key) => `${key} = ?`)} WHERE channel = ? AND channel_id = ?`, values.concat([ channel, channelId ]))
        } else {
          keys.push('id')
          values.push(uuidV4())

          return SQLite.run(`INSERT INTO recommendations (${keys}) VALUES (${values.map(() => '?')})`, values)
            .catch((error) => {
              if (error.code !== 'SQLITE_CONSTRAINT') {
                throw error
              }
            })
        }
      })
      .then(() => this.findByChannelAndChannelId(channel, channelId))
  }

  findAll (page = 1, limit = 25, criteria = {}, select = [ '*' ], sort = 'last_checked_out_date', unique = false) {
    const offset = (page - 1) * limit

    let queryResults
    let queryTotalCount
    let params = []
    let _select = buildSelectClause(unique, select)
    if (!_.isEmpty(criteria)) {
      const _criteria = transformObjectToRow(_.clone(criteria))

      const keys = _.keys(_criteria)
      const values = _.values(_criteria)

      const where = buildWhereClause(keys, values)

      queryResults = `SELECT ${unique ? 'DISTINCT' : ''} ${_select} FROM recommendations WHERE ${where} ORDER BY ${sort} DESC LIMIT ${limit} OFFSET ${offset}`
      queryTotalCount = `SELECT ${unique ? 'DISTINCT' : ''} COUNT(*) as count FROM recommendations WHERE ${where}`
      params = params.concat(values)
    } else {
      queryResults = `SELECT ${unique ? 'DISTINCT' : ''} ${_select} FROM recommendations ORDER BY ${sort} DESC LIMIT ${limit} OFFSET ${offset}`
      queryTotalCount = `SELECT ${unique ? 'DISTINCT' : ''} COUNT(*) as count FROM recommendations`
    }

    return Promise.props({
      results: queryAll.bind(this)(queryResults, params),
      totalCount: SQLite.get(queryTotalCount, params).then(({ count }) => count)
    })
      .catch((error) => {
        if (error.code !== 'SQLITE_ERROR') {
          throw error
        }

        return { results: [], totalCount: 0 }
      })
  }

  findById (id) {
    return SQLite.get('SELECT * FROM recommendations WHERE id = ?', [ id ])
      .then((row) => transformRowToObject(row))
  }

  findByChannelAndChannelId (channel, channelId) {
    return SQLite.get('SELECT * FROM recommendations WHERE channel = ? AND channel_id = ?', [ channel, channelId ])
      .then((row) => transformRowToObject(row))
  }
}

module.exports = new Recommendations()
<commit_msg>fix: train stats<commit_after>/*
 * Copyright (c) 2017, Hugo Freire <hugo@exec.sh>.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

const _ = require('lodash')
const Promise = require('bluebird')

const SQLite = require('./sqlite')

const uuidV4 = require('uuid/v4')

const transformRowToObject = function (row) {
  if (!row) {
    return
  }

  if (row.created_date) {
    row.created_date = new Date(row.created_date)
  }

  if (row.updated_date) {
    row.updated_date = new Date(row.updated_date)
  }

  if (row.liked_date) {
    row.liked_date = new Date(row.liked_date)
  }

  if (row.matched_date) {
    row.matched_date = new Date(row.matched_date)
  }

  if (row.trained_date) {
    row.trained_date = new Date(row.trained_date)
  }

  if (row.last_checked_out_date) {
    row.last_checked_out_date = new Date(row.last_checked_out_date)
  }

  if (row.data) {
    row.data = JSON.parse(row.data)
  }

  return row
}

const transformObjectToRow = function (object) {
  if (!object) {
    return
  }

  if (object.created_date instanceof Date) {
    object.created_date = object.created_date.toISOString()
  }

  if (object.updated_date instanceof Date) {
    object.updated_date = object.updated_date.toISOString()
  }

  if (object.matched_date instanceof Date) {
    object.matched_date = object.matched_date.toISOString()
  }

  if (object.liked_date instanceof Date) {
    object.liked_date = object.liked_date.toISOString()
  }

  if (object.trained_date instanceof Date) {
    object.trained_date = object.trained_date.toISOString()
  }

  if (object.last_checked_out_date instanceof Date) {
    object.last_checked_out_date = object.last_checked_out_date.toISOString()
  }

  if (object.data) {
    object.data = JSON.stringify(object.data)
  }

  return object
}

const queryAll = function (...args) {
  return SQLite.all(...args)
    .mapSeries((row) => transformRowToObject(row))
}

const buildWhereClause = (keys, values) => {
  if (_.includes(keys, 'liked_date')) {
    const index = _.indexOf(keys, 'liked_date')
    values[ index ] = values[ index ].split(' ')[ 0 ]
    values.splice(index, 0, values[ index ])
  }

  if (_.includes(keys, 'matched_date')) {
    const index = _.indexOf(keys, 'matched_date')
    values[ index ] = values[ index ].split(' ')[ 0 ]
    values.splice(index, 0, values[ index ])
  }

  if (_.includes(keys, 'last_checked_out_date')) {
    const index = _.indexOf(keys, 'last_checked_out_date')
    values[ index ] = values[ index ].split(' ')[ 0 ]
    values.splice(index, 0, values[ index ])
  }

  return keys.map((key) => {
    if (key === 'liked_date') {
      return `${key} > ? AND ${key} < strftime('%Y-%m-%dT%H:%M:%fZ', datetime(?, '+1 day'))`
    }

    if (key === 'matched_date') {
      return `${key} > ? AND ${key} < strftime('%Y-%m-%dT%H:%M:%fZ', datetime(?, '+1 day'))`
    }

    if (key === 'trained_date') {
      return `${key} < strftime('%Y-%m-%dT%H:%M:%fZ', datetime(?, '+1 day'))`
    }

    if (key === 'last_checked_out_date') {
      return `${key} > ? AND ${key} < strftime('%Y-%m-%dT%H:%M:%fZ', datetime(?, '+1 day'))`
    }

    return `${key} = ?`
  })
    .toString()
    .replace(/,/g, ' AND ')
    .replace(/datetime\(\? AND/, 'datetime(?,')
    // eslint-disable-next-line no-regex-spaces
    .replace(/AND  datetime/, ', datetime')
}

const buildSelectClause = (unique, select) => {
  if (unique && _.includes(select, 'liked_date')) {
    const index = _.indexOf(select, 'liked_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS liked_date`
  }

  if (unique && _.includes(select, 'matched_date')) {
    const index = _.indexOf(select, 'matched_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS matched_date`
  }

  if (unique && _.includes(select, 'trained_date')) {
    const index = _.indexOf(select, 'trained_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS trained_date`
  }

  if (unique && _.includes(select, 'last_checked_out_date')) {
    const index = _.indexOf(select, 'last_checked_out_date')
    select[ index ] = `strftime('%Y-%m-%d',${select[ index ]}) AS last_checked_out_date`
  }

  return select
}

class Recommendations {
  save (channel, channelId, data) {
    const _data = transformObjectToRow(_.clone(data))

    const keys = _.keys(_data)
    const values = _.values(_data)

    return Promise.resolve()
      .then(() => this.findByChannelAndChannelId(channel, channelId))
      .then((person) => {
        if (person) {
          if (_.includes(keys, 'updated_date')) {
            const index = _.indexOf(keys, 'updated_date')
            values[ index ] = new Date().toISOString()
          } else {
            keys.push('updated_date')
            values.push(new Date().toISOString())
          }

          return SQLite.run(`UPDATE recommendations SET ${keys.map((key) => `${key} = ?`)} WHERE channel = ? AND channel_id = ?`, values.concat([ channel, channelId ]))
        } else {
          keys.push('id')
          values.push(uuidV4())

          return SQLite.run(`INSERT INTO recommendations (${keys}) VALUES (${values.map(() => '?')})`, values)
            .catch((error) => {
              if (error.code !== 'SQLITE_CONSTRAINT') {
                throw error
              }
            })
        }
      })
      .then(() => this.findByChannelAndChannelId(channel, channelId))
  }

  findAll (page = 1, limit = 25, criteria = {}, select = [ '*' ], sort = 'last_checked_out_date', unique = false) {
    const offset = (page - 1) * limit

    let queryResults
    let queryTotalCount
    let params = []
    let _select = buildSelectClause(unique, select)
    if (!_.isEmpty(criteria)) {
      const _criteria = transformObjectToRow(_.clone(criteria))

      const keys = _.keys(_criteria)
      const values = _.values(_criteria)

      const where = buildWhereClause(keys, values)

      queryResults = `SELECT ${unique ? 'DISTINCT' : ''} ${_select} FROM recommendations WHERE ${where} ORDER BY ${sort} DESC LIMIT ${limit} OFFSET ${offset}`
      queryTotalCount = `SELECT ${unique ? 'DISTINCT' : ''} COUNT(*) as count FROM recommendations WHERE ${where}`
      params = params.concat(values)
    } else {
      queryResults = `SELECT ${unique ? 'DISTINCT' : ''} ${_select} FROM recommendations ORDER BY ${sort} DESC LIMIT ${limit} OFFSET ${offset}`
      queryTotalCount = `SELECT ${unique ? 'DISTINCT' : ''} COUNT(*) as count FROM recommendations`
    }

    return Promise.props({
      results: queryAll.bind(this)(queryResults, params),
      totalCount: SQLite.get(queryTotalCount, params).then(({ count }) => count)
    })
      .catch((error) => {
        if (error.code !== 'SQLITE_ERROR') {
          throw error
        }

        return { results: [], totalCount: 0 }
      })
  }

  findById (id) {
    return SQLite.get('SELECT * FROM recommendations WHERE id = ?', [ id ])
      .then((row) => transformRowToObject(row))
  }

  findByChannelAndChannelId (channel, channelId) {
    return SQLite.get('SELECT * FROM recommendations WHERE channel = ? AND channel_id = ?', [ channel, channelId ])
      .then((row) => transformRowToObject(row))
  }
}

module.exports = new Recommendations()
<|endoftext|>


<commit_before>goog.provide('ngeo.ScaleselectorController');
goog.provide('ngeo.ScaleselectorOptions');
goog.provide('ngeo.scaleselectorDirective');

goog.require('goog.events');
goog.require('goog.events.Key');
goog.require('ngeo');
goog.require('ol.Map');
goog.require('ol.Object');

ngeoModule.value('ngeoScaleselectorTemplateUrl',
    /**
     * @param {angular.JQLite} element Element.
     * @param {angular.Attributes} attrs Attributes.
     */
    function(element, attrs) {
      var templateUrl = attrs['ngeoScaleselectorTemplateurl'];
      return templateUrl !== undefined ? templateUrl :
          ngeo.baseTemplateUrl + '/scaleselector.html';
    });

/**
 * @typedef {{dropup: (boolean|undefined)}}
 */
ngeo.ScaleselectorOptions;

/**
 * Provides the "ngeoScaleselector" directive, a widget for
 * selecting map scales.
 *
 * Example:
 *
 *

 *

 *
 * The expression passed to the ngeo-scaleselector attribute should return an
 * object of this form:
 *
 *     {
 *       '0': $sce.trustAsHtml('1 : 200\'000\'000'),
 *       '1': $sce.trustAsHtml('1 : 100\'000\'000'),
 *       '2': $sce.trustAsHtml('1 : 50\'000\'000'),
 *       '3': $sce.trustAsHtml('1 : 25\'000\'000'),
 *       '4': $sce.trustAsHtml('1 : 12\'000\'000')
 *     }
 *
 * This object's keys are strings representing zoom levels, the values are
 * strings representing scales. The directive's partial uses ng-bind-html so
 * the scale strings should be trusted.
 *
 * That directive's partial uses Bootstrap's `dropdown` and `dropdown-menu`
 * classes, and `data-toggle="dropdown"`, so it is meant to be used with
 * Bootstrap's "dropdown" jQuery plugin.
 *
 * By default the directive uses "scaleselector.html" as its templateUrl. This
 * an be changed by redefining the "ngeoScaleselectorTemplateUrl" value.
 *
 * The directive has its own scope, but it is not isolate scope. That scope
 * includes a reference to the directive's controller: the "scaleselectorCtrl"
 * scope property.
 *
 * The directive doesn't create any watcher. In particular the object including
 * the scales information is now watched.
 *
 * @param {string|function(!angular.JQLite=, !angular.Attributes=)}
 *     ngeoScaleselectorTemplateUrl Template URL for the directive.
 * @return {angular.Directive} Directive Definition Object.
 * @ngInject
 * @ngdoc directive
 * @ngname ngeoScaleselector
 */
ngeo.scaleselectorDirective = function(ngeoScaleselectorTemplateUrl) {
  return {
    restrict: 'A',
    scope: true,
    controller: 'NgeoScaleselectorController',
    templateUrl: ngeoScaleselectorTemplateUrl
  };
};

ngeoModule.directive('ngeoScaleselector', ngeo.scaleselectorDirective);

/**
 * @constructor
 * @param {angular.Scope} $scope Directive scope.
 * @param {angular.JQLite} $element Element.
 * @param {angular.Attributes} $attrs Attributes.
 * @export
 * @ngInject
 * @ngdoc controller
 * @ngname NgeoScaleselectorController
 */
ngeo.ScaleselectorController = function($scope, $element, $attrs) {

  var scalesExpr = $attrs['ngeoScaleselector'];

  /**
   * The zoom level/scale map object.
   * @type {Object.<string, string>}
   * @export
   */
  this.scales = /** @type {Object.<string, string>} */
      ($scope.$eval(scalesExpr));
  goog.asserts.assert(goog.isDef(this['scales']));

  var zoomLevels = Object.keys(this['scales']).map(Number);
  zoomLevels.sort();

  /**
   * @type {Array.<number>}
   * @export
   */
  this.zoomLevels = zoomLevels;

  var mapExpr = $attrs['ngeoScaleselectorMap'];

  /**
   * @type {ol.Map}
   * @private
   */
  this.map_ = /** @type {ol.Map} */ ($scope.$eval(mapExpr));
  goog.asserts.assertInstanceof(this.map_, ol.Map);

  var optionsExpr = $attrs['ngeoScaleselectorOptions'];
  var options = $scope.$eval(optionsExpr);

  /**
   * @type {!ngeo.ScaleselectorOptions}
   * @export
   */
  this.options = ngeo.ScaleselectorController.getOptions_(options);

  /**
   * @type {angular.Scope}
   * @private
   */
  this.$scope_ = $scope;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.resolutionChangeKey_ = null;

  /**
   * @type {string|undefined}
   * @export
   */
  this.currentScale = undefined;

  var view = this.map_.getView();
  if (!goog.isNull(view)) {
    var currentZoom = this.map_.getView().getZoom();
    if (goog.isDef(currentZoom)) {
      this.currentScale = this.getScale(currentZoom);
    }
  }

  goog.events.listen(this.map_, ol.Object.getChangeEventType('view'),
      this.handleViewChange_, false, this);

  this.registerResolutionChangeListener_();

  $scope['scaleselectorCtrl'] = this;

};

/**
 * @param {?} options Options after expression evaluation.
 * @return {!ngeo.ScaleselectorOptions} Options object.
 * @private
 */
ngeo.ScaleselectorController.getOptions_ = function(options) {
  var ret;
  if (!goog.isDef(options)) {
    ret = {'dropup': false};
  } else {
    if (!goog.isDef(options['dropup'])) {
      options['dropup'] = false;
    }
    ret = /** @type {ngeo.ScaleselectorOptions} */ (options);
  }
  return ret;
};

/**
 * @param {number} zoom Zoom level.
 * @return {string} Scale.
 * @export
 */
ngeo.ScaleselectorController.prototype.getScale = function(zoom) {
  return this.scales[zoom.toString()];
};

/**
 * @param {number} zoom Zoom level.
 * @export
 */
ngeo.ScaleselectorController.prototype.changeZoom = function(zoom) {
  this.map_.getView().setZoom(zoom);
};

/**
 * @param {ol.ObjectEvent} e OpenLayers ObjectEvent.
 * @private
 */
ngeo.ScaleselectorController.prototype.handleResolutionChange_ = function(e) {
  var view = this.map_.getView();
  var currentScale = this.scales[view.getZoom().toString()];

  // handleResolutionChange_ is a change:resolution listener. The listener
  // may be executed outside the Angular context, for example when the user
  // double-clicks to zoom on the map.
  //
  // But it may also be executed inside the Angular context, when a function
  // in Angular context calls setZoom or setResolution on the view, which
  // is for example what happens when this controller's changeZoom function
  // is called.
  //
  // For that reason we use $applyAsync instead of $apply here.

  this.$scope_.$applyAsync(
      /** @type {function(?)} */ (
      goog.bind(function() {
        this.currentScale = currentScale;
      }, this)));
};

/**
 * @param {ol.ObjectEvent} e OpenLayers ObjectEvent.
 * @private
 */
ngeo.ScaleselectorController.prototype.handleViewChange_ = function(e) {
  this.registerResolutionChangeListener_();
};

/**
 * @private
 */
ngeo.ScaleselectorController.prototype.registerResolutionChangeListener_ =
    function() {
  if (!goog.isNull(this.resolutionChangeKey_)) {
    goog.events.unlistenByKey(this.resolutionChangeKey_);
  }
  var view = this.map_.getView();
  this.resolutionChangeKey_ = goog.events.listen(view,
      ol.Object.getChangeEventType('resolution'), this.handleResolutionChange_,
      false, this);
};

ngeoModule.controller('NgeoScaleselectorController',
    ngeo.ScaleselectorController);
<commit_msg>Use the dotted notation for accessing this.scales<commit_after>goog.provide('ngeo.ScaleselectorController');
goog.provide('ngeo.ScaleselectorOptions');
goog.provide('ngeo.scaleselectorDirective');

goog.require('goog.events');
goog.require('goog.events.Key');
goog.require('ngeo');
goog.require('ol.Map');
goog.require('ol.Object');

ngeoModule.value('ngeoScaleselectorTemplateUrl',
    /**
     * @param {angular.JQLite} element Element.
     * @param {angular.Attributes} attrs Attributes.
     */
    function(element, attrs) {
      var templateUrl = attrs['ngeoScaleselectorTemplateurl'];
      return templateUrl !== undefined ? templateUrl :
          ngeo.baseTemplateUrl + '/scaleselector.html';
    });

/**
 * @typedef {{dropup: (boolean|undefined)}}
 */
ngeo.ScaleselectorOptions;

/**
 * Provides the "ngeoScaleselector" directive, a widget for
 * selecting map scales.
 *
 * Example:
 *
 *

 *

 *
 * The expression passed to the ngeo-scaleselector attribute should return an
 * object of this form:
 *
 *     {
 *       '0': $sce.trustAsHtml('1 : 200\'000\'000'),
 *       '1': $sce.trustAsHtml('1 : 100\'000\'000'),
 *       '2': $sce.trustAsHtml('1 : 50\'000\'000'),
 *       '3': $sce.trustAsHtml('1 : 25\'000\'000'),
 *       '4': $sce.trustAsHtml('1 : 12\'000\'000')
 *     }
 *
 * This object's keys are strings representing zoom levels, the values are
 * strings representing scales. The directive's partial uses ng-bind-html so
 * the scale strings should be trusted.
 *
 * That directive's partial uses Bootstrap's `dropdown` and `dropdown-menu`
 * classes, and `data-toggle="dropdown"`, so it is meant to be used with
 * Bootstrap's "dropdown" jQuery plugin.
 *
 * By default the directive uses "scaleselector.html" as its templateUrl. This
 * an be changed by redefining the "ngeoScaleselectorTemplateUrl" value.
 *
 * The directive has its own scope, but it is not isolate scope. That scope
 * includes a reference to the directive's controller: the "scaleselectorCtrl"
 * scope property.
 *
 * The directive doesn't create any watcher. In particular the object including
 * the scales information is now watched.
 *
 * @param {string|function(!angular.JQLite=, !angular.Attributes=)}
 *     ngeoScaleselectorTemplateUrl Template URL for the directive.
 * @return {angular.Directive} Directive Definition Object.
 * @ngInject
 * @ngdoc directive
 * @ngname ngeoScaleselector
 */
ngeo.scaleselectorDirective = function(ngeoScaleselectorTemplateUrl) {
  return {
    restrict: 'A',
    scope: true,
    controller: 'NgeoScaleselectorController',
    templateUrl: ngeoScaleselectorTemplateUrl
  };
};

ngeoModule.directive('ngeoScaleselector', ngeo.scaleselectorDirective);

/**
 * @constructor
 * @param {angular.Scope} $scope Directive scope.
 * @param {angular.JQLite} $element Element.
 * @param {angular.Attributes} $attrs Attributes.
 * @export
 * @ngInject
 * @ngdoc controller
 * @ngname NgeoScaleselectorController
 */
ngeo.ScaleselectorController = function($scope, $element, $attrs) {

  var scalesExpr = $attrs['ngeoScaleselector'];

  /**
   * The zoom level/scale map object.
   * @type {!Object.<string, string>}
   * @export
   */
  this.scales = /** @type {!Object.<string, string>} */
      ($scope.$eval(scalesExpr));
  goog.asserts.assert(goog.isDef(this.scales));

  var zoomLevels = Object.keys(this.scales).map(Number);
  zoomLevels.sort();

  /**
   * @type {Array.<number>}
   * @export
   */
  this.zoomLevels = zoomLevels;

  var mapExpr = $attrs['ngeoScaleselectorMap'];

  /**
   * @type {ol.Map}
   * @private
   */
  this.map_ = /** @type {ol.Map} */ ($scope.$eval(mapExpr));
  goog.asserts.assertInstanceof(this.map_, ol.Map);

  var optionsExpr = $attrs['ngeoScaleselectorOptions'];
  var options = $scope.$eval(optionsExpr);

  /**
   * @type {!ngeo.ScaleselectorOptions}
   * @export
   */
  this.options = ngeo.ScaleselectorController.getOptions_(options);

  /**
   * @type {angular.Scope}
   * @private
   */
  this.$scope_ = $scope;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.resolutionChangeKey_ = null;

  /**
   * @type {string|undefined}
   * @export
   */
  this.currentScale = undefined;

  var view = this.map_.getView();
  if (!goog.isNull(view)) {
    var currentZoom = this.map_.getView().getZoom();
    if (goog.isDef(currentZoom)) {
      this.currentScale = this.getScale(currentZoom);
    }
  }

  goog.events.listen(this.map_, ol.Object.getChangeEventType('view'),
      this.handleViewChange_, false, this);

  this.registerResolutionChangeListener_();

  $scope['scaleselectorCtrl'] = this;

};

/**
 * @param {?} options Options after expression evaluation.
 * @return {!ngeo.ScaleselectorOptions} Options object.
 * @private
 */
ngeo.ScaleselectorController.getOptions_ = function(options) {
  var ret;
  if (!goog.isDef(options)) {
    ret = {'dropup': false};
  } else {
    if (!goog.isDef(options['dropup'])) {
      options['dropup'] = false;
    }
    ret = /** @type {ngeo.ScaleselectorOptions} */ (options);
  }
  return ret;
};

/**
 * @param {number} zoom Zoom level.
 * @return {string} Scale.
 * @export
 */
ngeo.ScaleselectorController.prototype.getScale = function(zoom) {
  return this.scales[zoom.toString()];
};

/**
 * @param {number} zoom Zoom level.
 * @export
 */
ngeo.ScaleselectorController.prototype.changeZoom = function(zoom) {
  this.map_.getView().setZoom(zoom);
};

/**
 * @param {ol.ObjectEvent} e OpenLayers ObjectEvent.
 * @private
 */
ngeo.ScaleselectorController.prototype.handleResolutionChange_ = function(e) {
  var view = this.map_.getView();
  var currentScale = this.scales[view.getZoom().toString()];

  // handleResolutionChange_ is a change:resolution listener. The listener
  // may be executed outside the Angular context, for example when the user
  // double-clicks to zoom on the map.
  //
  // But it may also be executed inside the Angular context, when a function
  // in Angular context calls setZoom or setResolution on the view, which
  // is for example what happens when this controller's changeZoom function
  // is called.
  //
  // For that reason we use $applyAsync instead of $apply here.

  this.$scope_.$applyAsync(
      /** @type {function(?)} */ (
      goog.bind(function() {
        this.currentScale = currentScale;
      }, this)));
};

/**
 * @param {ol.ObjectEvent} e OpenLayers ObjectEvent.
 * @private
 */
ngeo.ScaleselectorController.prototype.handleViewChange_ = function(e) {
  this.registerResolutionChangeListener_();
};

/**
 * @private
 */
ngeo.ScaleselectorController.prototype.registerResolutionChangeListener_ =
    function() {
  if (!goog.isNull(this.resolutionChangeKey_)) {
    goog.events.unlistenByKey(this.resolutionChangeKey_);
  }
  var view = this.map_.getView();
  this.resolutionChangeKey_ = goog.events.listen(view,
      ol.Object.getChangeEventType('resolution'), this.handleResolutionChange_,
      false, this);
};

ngeoModule.controller('NgeoScaleselectorController',
    ngeo.ScaleselectorController);
<|endoftext|>


<commit_before>import 'elix/define/DropdownList.js';

const template = document.createElement('template');
template.innerHTML = `

  Label
  <elix-dropdown-list>
    <slot></slot>
  </elix-dropdown-list>

`;
/**
 * @class SeList
 */
export class SeList extends HTMLElement {
  /**
    * @function constructor
    */
  constructor () {
    super();
    // create the shadowDom and insert the template
    this._shadowRoot = this.attachShadow({ mode: 'open' });
    this._shadowRoot.append(template.content.cloneNode(true));
    this.$dropdown = this._shadowRoot.querySelector('elix-dropdown-list');
    this.$label = this._shadowRoot.querySelector('label');
  }
  /**
   * @function observedAttributes
   * @returns {any} observed
   */
  static get observedAttributes () {
    return [ 'label', 'width', 'height' ];
  }

  /**
   * @function attributeChangedCallback
   * @param {string} name
   * @param {string} oldValue
   * @param {string} newValue
   * @returns {void}
   */
  attributeChangedCallback (name, oldValue, newValue) {
    if (oldValue === newValue) return;
    switch (name) {
    case 'label':
      this.$label.textContent = newValue;
      break;
    case 'height':
      this.$dropdown.style.height = newValue;
      break;
    case 'width':
      this.$dropdown.style.width = newValue;
      break;
    default:
      // eslint-disable-next-line no-console
      console.error(`unknown attribute: ${name}`);
      break;
    }
  }
  /**
   * @function get
   * @returns {any}
   */
  get label () {
    return this.getAttribute('label');
  }

  /**
   * @function set
   * @returns {void}
   */
  set label (value) {
    this.setAttribute('label', value);
  }
  /**
   * @function get
   * @returns {any}
   */
  get width () {
    return this.getAttribute('width');
  }

  /**
   * @function set
   * @returns {void}
   */
  set width (value) {
    this.setAttribute('width', value);
  }
  /**
   * @function get
   * @returns {any}
   */
  get height () {
    return this.getAttribute('height');
  }

  /**
   * @function set
   * @returns {void}
   */
  set height (value) {
    this.setAttribute('height', value);
  }
  /**
   * @function connectedCallback
   * @returns {void}
   */
  connectedCallback () {
    const currentObj = this;
    this.$dropdown.addEventListener('selectedindexchange', (e) => {
      if (e?.detail?.selectedIndex !== undefined) {
        const value = this.$dropdown.selectedItem.getAttribute('value');
        const closeEvent = new CustomEvent('change', { detail: { value } });
        currentObj.dispatchEvent(closeEvent);
        currentObj.value = value;
      }
    });
    this.$dropdown.addEventListener('close', (_e) => {
      /** with Chrome, selectedindexchange does not fire consistently
      * unless you forec change in this close event
      */
      this.$dropdown.selectedIndex = this.$dropdown.currentIndex;
    });
  }
}

// Register
customElements.define('se-list', SeList);
<commit_msg>Update seList.js<commit_after>import 'elix/define/DropdownList.js';

const template = document.createElement('template');
template.innerHTML = `

  Label
  <elix-dropdown-list>
    <slot></slot>
  </elix-dropdown-list>

`;
/**
 * @class SeList
 */
export class SeList extends HTMLElement {
  /**
    * @function constructor
    */
  constructor () {
    super();
    // create the shadowDom and insert the template
    this._shadowRoot = this.attachShadow({ mode: 'open' });
    this._shadowRoot.append(template.content.cloneNode(true));
    this.$dropdown = this._shadowRoot.querySelector('elix-dropdown-list');
    this.$label = this._shadowRoot.querySelector('label');
  }
  /**
   * @function observedAttributes
   * @returns {any} observed
   */
  static get observedAttributes () {
    return [ 'label', 'width', 'height' ];
  }

  /**
   * @function attributeChangedCallback
   * @param {string} name
   * @param {string} oldValue
   * @param {string} newValue
   * @returns {void}
   */
  attributeChangedCallback (name, oldValue, newValue) {
    if (oldValue === newValue) return;
    switch (name) {
    case 'label':
      this.$label.textContent = newValue;
      break;
    case 'height':
      this.$dropdown.style.height = newValue;
      break;
    case 'width':
      this.$dropdown.style.width = newValue;
      break;
    default:
      // eslint-disable-next-line no-console
      console.error(`unknown attribute: ${name}`);
      break;
    }
  }
  /**
   * @function get
   * @returns {any}
   */
  get label () {
    return this.getAttribute('label');
  }

  /**
   * @function set
   * @returns {void}
   */
  set label (value) {
    this.setAttribute('label', value);
  }
  /**
   * @function get
   * @returns {any}
   */
  get width () {
    return this.getAttribute('width');
  }

  /**
   * @function set
   * @returns {void}
   */
  set width (value) {
    this.setAttribute('width', value);
  }
  /**
   * @function get
   * @returns {any}
   */
  get height () {
    return this.getAttribute('height');
  }

  /**
   * @function set
   * @returns {void}
   */
  set height (value) {
    this.setAttribute('height', value);
  }
  /**
   * @function connectedCallback
   * @returns {void}
   */
  connectedCallback () {
    const currentObj = this;
    this.$dropdown.addEventListener('selectedindexchange', (e) => {
      if (e?.detail?.selectedIndex !== undefined) {
        const value = this.$dropdown.selectedItem.getAttribute('value');
        const closeEvent = new CustomEvent('change', { detail: { value } });
        currentObj.dispatchEvent(closeEvent);
        currentObj.value = value;
      }
    });
    this.$dropdown.addEventListener('close', (_e) => {
      /** with Chrome, selectedindexchange does not fire consistently
      * unless you forec change in this close event
      */
      this.$dropdown.selectedIndex = this.$dropdown.currentIndex;
    });
  }
}

// Register
customElements.define('se-list', SeList);
<|endoftext|>


<commit_before>/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
 * Copyright 2013 Mozilla Foundation
 *
 * 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.
 */
/*global renderDisplayObject, RenderVisitor, argbUintToStr, getBlendModeName */

var BitmapDataDefinition = (function () {
  function replaceRect(ctx, x, y, w, h, alpha) {
    if (alpha < 255) {
      ctx.clearRect(x, y, w, h);
    }
    if (alpha > 0) {
      ctx.fillRect(x, y, w, h);
    }
  }

  var def = {
    __class__: 'flash.display.BitmapData',

    initialize: function () {
      this._changeNotificationTarget = null;
      this._locked = false;

      if (this.symbol) {
        this._img = this.symbol.img;
        this._skipCopyToCanvas = this.symbol.skipCopyToCanvas;
      }
    },

    _checkCanvas: function() {
      if (this._drawable === null)
        throw ArgumentError();
    },

    ctor: function(width, height, transparent, backgroundColor) {
      if (this._img) {
        // the image can be HTML image or canvas
        width = this._img.naturalWidth || this._img.width;
        height = this._img.naturalHeight || this._img.height;
      } else if (isNaN(width + height) || width <= 0 || height <= 0) {
        throw ArgumentError();
      }

      this._transparent = transparent === undefined ? true : !!transparent;
      this._backgroundColor = backgroundColor === undefined ? 0xffffffff : backgroundColor;
      if (!this._transparent) {
        this._backgroundColor |= 0xff000000;
      }

      if (this._skipCopyToCanvas) {
        this._drawable = this._img;
      } else {
        var canvas = document.createElement('canvas');
        this._ctx = canvas.getContext('2d');
        canvas.width = width | 0;
        canvas.height = height | 0;
        this._drawable = canvas;
        if (!this._transparent || (!this._img && this._backgroundColor)) {
          this.fillRect(new flash.geom.Rectangle(0, 0, width | 0, height | 0), this._backgroundColor);
        }
        if (this._img) {
          this._ctx.drawImage(this._img, 0, 0);
        }
      }
    },
    dispose: function() {
      this._ctx = null;
      this._drawable.width = 0;
      this._drawable.height = 0;
      this._drawable = null;
    },
    draw: function(source, matrix, colorTransform, blendMode, clipRect, smoothing) {
      this._checkCanvas();
      this._ctx.save();
      this._ctx.beginPath();
      if (clipRect && clipRect.width > 0 && clipRect.height > 0) {
        this._ctx.rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
        this._ctx.clip();
      }
      if (matrix) {
        this._ctx.transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
      }
      this._ctx.globalCompositeOperation = getBlendModeName(blendMode);
      if (flash.display.BitmapData.class.isInstanceOf(source)) {
        this._ctx.drawImage(source._drawable, 0, 0);
      } else {
        (new RenderVisitor(source, this._ctx, null, true)).startFragment();
      }
      this._ctx.restore();
      this._invalidate();
    },
    fillRect: function(rect, color) {
      this._checkCanvas();
      if (!this._transparent) {
        color |= 0xff000000;
      }
      var ctx = this._ctx;
      ctx.fillStyle = argbUintToStr(color);
      replaceRect(ctx, rect.x, rect.y, rect.width, rect.height, color >>> 24 & 0xff);
      this._invalidate();
    },
    getPixel: function(x, y) {
      this._checkCanvas();
      var data = this._ctx.getImageData(x, y, 1, 1).data;
      return dataToRGB(data);
    },
    getPixel32: function(x, y) {
      this._checkCanvas();
      var data = this._ctx.getImageData(x, y, 1, 1).data;
      return dataToARGB(data);
    },
    _invalidate: function(changeRect) {
      if (changeRect) {
        somewhatImplemented("BitmapData._invalidate(changeRect)");
      }
      if (this._locked) {
        return;
      }
      if (this._changeNotificationTarget) {
        this._changeNotificationTarget._invalidate();
      }
    },
    setPixel: function(x, y, color) {
      this.fillRect({ x: x, y: y, width: 1, height: 1 }, color | 0xFF000000);
      this._invalidate();
    },
    setPixel32: function(x, y, color) {
      this.fillRect({ x: x, y: y, width: 1, height: 1 }, color);
      this._invalidate();
    },
    /**
     * Provides a fast routine to perform pixel manipulation between images with no stretching, rotation, or color effects.
     */
    copyPixels: function copyPixels(sourceBitmapData, sourceRect, destPoint, alphaBitmapData, alphaPoint, mergeAlpha) {
      if (alphaBitmapData) {
        notImplemented("BitmapData.copyPixels w/ alpha");
      }
      var w = sourceRect.width;
      var h = sourceRect.height;
      var sx = sourceRect.x;
      var sy = sourceRect.y;
      var dx = destPoint.x;
      var dy = destPoint.y;
      if (!mergeAlpha) {
        this._ctx.clearRect(dx, dy, w, h);
      }
      this._ctx.drawImage(sourceBitmapData._drawable, sx, sy, w, h, dx, dy, w, h);
      this._invalidate();
    },
    /**
     * Locks an image so that any objects that reference the BitmapData object, such as Bitmap objects,
     * are not updated when this BitmapData object changes.
     */
    lock: function lock() { // (void) -> void
      this._locked = true;
    },
    /**
     * Unlocks an image so that any objects that reference the BitmapData object, such as Bitmap
     * objects, are updated when this BitmapData object changes.
     */
    unlock: function unlock(changeRect) { // (changeRect:Rectangle = null) -> void
      this._locked = false;
      this._invalidate(changeRect);
    },
    clone: function() {
      this._checkCanvas();
      var bd = new flash.display.BitmapData(this._drawable.width, this._drawable.height, true, 0);
      bd._ctx.drawImage(this._drawable, 0, 0);
      return bd;
    },
    scroll: function(x, y) {
      this._checkCanvas();
      this._ctx.draw(this._drawable, x, y);
      this._ctx.save();
      var color = this._img ? 0 : this._backgroundColor;
      if (!this._transparent) {
        color |= 0xff000000;
      }
      var alpha = color >>> 24 & 0xff;
      this._ctx.fillStyle = argbUintToStr(color);
      var w = this._drawable.width;
      var h = this._drawable.height;
      if (x > 0) {
        replaceRect(this._ctx, 0, 0, x, h, alpha);
      } else if (x < 0) {
        replaceRect(this._ctx, w + x, 0, -x, h, alpha);
      }
      if (y > 0) {
        replaceRect(this._ctx, 0, 0, w, y, alpha);
      } else if (y < 0) {
        replaceRect(this._ctx, h + y, w, -y, alpha);
      }
      this._ctx.restore();
      this._invalidate();
    },
    get width() {
      return this._drawable.width;
    },
    get height() {
      return this._drawable.height;
    }
  };

  var desc = Object.getOwnPropertyDescriptor;

  def.__glue__ = {
    native: {
      instance: {
        ctor : def.ctor,
        fillRect : def.fillRect,
        dispose : def.dispose,
        getPixel : def.getPixel,
        getPixel32 : def.getPixel32,
        setPixel : def.setPixel,
        setPixel32 : def.setPixel32,
        copyPixels: def.copyPixels,
        lock: def.lock,
        unlock: def.unlock,
        draw : def.draw,
        clone : def.clone,
        scroll : def.scroll,
        width : desc(def, "width"),
        height : desc(def, "height")
      }
    }
  };

  return def;
}).call(this);

function dataToRGB(data) {
  return data[0] << 16 | data[1] << 8 | data[2];
}
function dataToARGB(data) {
  return data[3] << 24 | dataToRGB(data);
}
<commit_msg>Throw correct error for for invalid ctor arguments to BitmapData<commit_after>/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
 * Copyright 2013 Mozilla Foundation
 *
 * 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.
 */
/*global renderDisplayObject, RenderVisitor, argbUintToStr, getBlendModeName */

var BitmapDataDefinition = (function () {
  function replaceRect(ctx, x, y, w, h, alpha) {
    if (alpha < 255) {
      ctx.clearRect(x, y, w, h);
    }
    if (alpha > 0) {
      ctx.fillRect(x, y, w, h);
    }
  }

  var def = {
    __class__: 'flash.display.BitmapData',

    initialize: function () {
      this._changeNotificationTarget = null;
      this._locked = false;

      if (this.symbol) {
        this._img = this.symbol.img;
        this._skipCopyToCanvas = this.symbol.skipCopyToCanvas;
      }
    },

    _checkCanvas: function() {
      if (this._drawable === null)
        throw ArgumentError();
    },

    ctor: function(width, height, transparent, backgroundColor) {
      if (this._img) {
        // the image can be HTML image or canvas
        width = this._img.naturalWidth || this._img.width;
        height = this._img.naturalHeight || this._img.height;
      } else if (isNaN(width + height) || width <= 0 || height <= 0) {
        throwError('ArgumentError', Errors.ArgumentError);
      }

      this._transparent = transparent === undefined ? true : !!transparent;
      this._backgroundColor = backgroundColor === undefined ? 0xffffffff : backgroundColor;
      if (!this._transparent) {
        this._backgroundColor |= 0xff000000;
      }

      if (this._skipCopyToCanvas) {
        this._drawable = this._img;
      } else {
        var canvas = document.createElement('canvas');
        this._ctx = canvas.getContext('2d');
        canvas.width = width | 0;
        canvas.height = height | 0;
        this._drawable = canvas;
        if (!this._transparent || (!this._img && this._backgroundColor)) {
          this.fillRect(new flash.geom.Rectangle(0, 0, width | 0, height | 0), this._backgroundColor);
        }
        if (this._img) {
          this._ctx.drawImage(this._img, 0, 0);
        }
      }
    },
    dispose: function() {
      this._ctx = null;
      this._drawable.width = 0;
      this._drawable.height = 0;
      this._drawable = null;
    },
    draw: function(source, matrix, colorTransform, blendMode, clipRect, smoothing) {
      this._checkCanvas();
      this._ctx.save();
      this._ctx.beginPath();
      if (clipRect && clipRect.width > 0 && clipRect.height > 0) {
        this._ctx.rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
        this._ctx.clip();
      }
      if (matrix) {
        this._ctx.transform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
      }
      this._ctx.globalCompositeOperation = getBlendModeName(blendMode);
      if (flash.display.BitmapData.class.isInstanceOf(source)) {
        this._ctx.drawImage(source._drawable, 0, 0);
      } else {
        (new RenderVisitor(source, this._ctx, null, true)).startFragment();
      }
      this._ctx.restore();
      this._invalidate();
    },
    fillRect: function(rect, color) {
      this._checkCanvas();
      if (!this._transparent) {
        color |= 0xff000000;
      }
      var ctx = this._ctx;
      ctx.fillStyle = argbUintToStr(color);
      replaceRect(ctx, rect.x, rect.y, rect.width, rect.height, color >>> 24 & 0xff);
      this._invalidate();
    },
    getPixel: function(x, y) {
      this._checkCanvas();
      var data = this._ctx.getImageData(x, y, 1, 1).data;
      return dataToRGB(data);
    },
    getPixel32: function(x, y) {
      this._checkCanvas();
      var data = this._ctx.getImageData(x, y, 1, 1).data;
      return dataToARGB(data);
    },
    _invalidate: function(changeRect) {
      if (changeRect) {
        somewhatImplemented("BitmapData._invalidate(changeRect)");
      }
      if (this._locked) {
        return;
      }
      if (this._changeNotificationTarget) {
        this._changeNotificationTarget._invalidate();
      }
    },
    setPixel: function(x, y, color) {
      this.fillRect({ x: x, y: y, width: 1, height: 1 }, color | 0xFF000000);
      this._invalidate();
    },
    setPixel32: function(x, y, color) {
      this.fillRect({ x: x, y: y, width: 1, height: 1 }, color);
      this._invalidate();
    },
    /**
     * Provides a fast routine to perform pixel manipulation between images with no stretching, rotation, or color effects.
     */
    copyPixels: function copyPixels(sourceBitmapData, sourceRect, destPoint, alphaBitmapData, alphaPoint, mergeAlpha) {
      if (alphaBitmapData) {
        notImplemented("BitmapData.copyPixels w/ alpha");
      }
      var w = sourceRect.width;
      var h = sourceRect.height;
      var sx = sourceRect.x;
      var sy = sourceRect.y;
      var dx = destPoint.x;
      var dy = destPoint.y;
      if (!mergeAlpha) {
        this._ctx.clearRect(dx, dy, w, h);
      }
      this._ctx.drawImage(sourceBitmapData._drawable, sx, sy, w, h, dx, dy, w, h);
      this._invalidate();
    },
    /**
     * Locks an image so that any objects that reference the BitmapData object, such as Bitmap objects,
     * are not updated when this BitmapData object changes.
     */
    lock: function lock() { // (void) -> void
      this._locked = true;
    },
    /**
     * Unlocks an image so that any objects that reference the BitmapData object, such as Bitmap
     * objects, are updated when this BitmapData object changes.
     */
    unlock: function unlock(changeRect) { // (changeRect:Rectangle = null) -> void
      this._locked = false;
      this._invalidate(changeRect);
    },
    clone: function() {
      this._checkCanvas();
      var bd = new flash.display.BitmapData(this._drawable.width, this._drawable.height, true, 0);
      bd._ctx.drawImage(this._drawable, 0, 0);
      return bd;
    },
    scroll: function(x, y) {
      this._checkCanvas();
      this._ctx.draw(this._drawable, x, y);
      this._ctx.save();
      var color = this._img ? 0 : this._backgroundColor;
      if (!this._transparent) {
        color |= 0xff000000;
      }
      var alpha = color >>> 24 & 0xff;
      this._ctx.fillStyle = argbUintToStr(color);
      var w = this._drawable.width;
      var h = this._drawable.height;
      if (x > 0) {
        replaceRect(this._ctx, 0, 0, x, h, alpha);
      } else if (x < 0) {
        replaceRect(this._ctx, w + x, 0, -x, h, alpha);
      }
      if (y > 0) {
        replaceRect(this._ctx, 0, 0, w, y, alpha);
      } else if (y < 0) {
        replaceRect(this._ctx, h + y, w, -y, alpha);
      }
      this._ctx.restore();
      this._invalidate();
    },
    get width() {
      return this._drawable.width;
    },
    get height() {
      return this._drawable.height;
    }
  };

  var desc = Object.getOwnPropertyDescriptor;

  def.__glue__ = {
    native: {
      instance: {
        ctor : def.ctor,
        fillRect : def.fillRect,
        dispose : def.dispose,
        getPixel : def.getPixel,
        getPixel32 : def.getPixel32,
        setPixel : def.setPixel,
        setPixel32 : def.setPixel32,
        copyPixels: def.copyPixels,
        lock: def.lock,
        unlock: def.unlock,
        draw : def.draw,
        clone : def.clone,
        scroll : def.scroll,
        width : desc(def, "width"),
        height : desc(def, "height")
      }
    }
  };

  return def;
}).call(this);

function dataToRGB(data) {
  return data[0] << 16 | data[1] << 8 | data[2];
}
function dataToARGB(data) {
  return data[3] << 24 | dataToRGB(data);
}
<|endoftext|>


<commit_before>/*

    Given a network of pipes, simulate fluid moving through them.

    Pipe object:
        va
        vb
            vertex A, and vertex B objects, eg {x: 0, y: 10}
        ca
        cb
            array of indexes for pipes that share vertex A and vertex B respectively

    Initialise with an object containing:
        pipes
            array of Pipe objects
        gravity
            float defaults to 0.1

    Returns the an object with the methods:
        addFluid
            add a volume of fluid to any pipe
        update
            update the simulation
*/

var VoronoiDrip = VoronoiDrip || {};
VoronoiDrip.FluidNetworkSimulation = VoronoiDrip.FluidNetworkSimulation || {};

VoronoiDrip.FluidNetworkSimulation.MINIMUM_FLUID_VOLUME = 0.00001;
VoronoiDrip.FluidNetworkSimulation.GRAVITY = 5;

VoronoiDrip.FluidNetworkSimulation.create = function(spec) {
    var that = {};

    that.pipes = spec.pipes;
    that.gravity = spec.hasOwnProperty('gravity') && spec.gravity ? spec.gravity : VoronoiDrip.FluidNetworkSimulation.GRAVITY;

    that.addFluid = function(pipe, point, volume) {
        that.fluidAdder.add(pipe, point, volume);
    };

    that.start = function() {
        var fns = VoronoiDrip.FluidNetworkSimulation;
        that.metrics = fns.Metrics.create({
            pipes: that.pipes,
            gravity: that.gravity
        });
        that.metrics.start();
        that.overlapSolver = fns.OverlapSolver.create({
            pipes: that.pipes
        });
        that.fluidAdder = fns.FluidAdder.create({
            pipes: that.pipes,
            metrics: that.metrics,
            overlapSolver: that.overlapSolver
        });
        that.targetCalculator = fns.TargetCalculator.create({
            pipes: that.pipes,
            metrics: that.metrics
        });
        that.pressureSolver = fns.PressureSolver.create({
            pipes: that.pipes,
            metrics: that.metrics,
            fluidAdder: that.fluidAdder,
            targetCalculator: that.targetCalculator
        });
        that.fluidMover = fns.FluidMover.create({
            pipes: that.pipes,
            metrics: that.metrics,
            pressureSolver: that.pressureSolver
        });
    };

    that.update = function() {
        that.fluidMover.update();
    };

    return that;
};<commit_msg>Include the target calculator in the fluid adder when creating the simulation<commit_after>/*

    Given a network of pipes, simulate fluid moving through them.

    Pipe object:
        va
        vb
            vertex A, and vertex B objects, eg {x: 0, y: 10}
        ca
        cb
            array of indexes for pipes that share vertex A and vertex B respectively

    Initialise with an object containing:
        pipes
            array of Pipe objects
        gravity
            float defaults to 0.1

    Returns the an object with the methods:
        addFluid
            add a volume of fluid to any pipe
        update
            update the simulation
*/

var VoronoiDrip = VoronoiDrip || {};
VoronoiDrip.FluidNetworkSimulation = VoronoiDrip.FluidNetworkSimulation || {};

VoronoiDrip.FluidNetworkSimulation.MINIMUM_FLUID_VOLUME = 0.00001;
VoronoiDrip.FluidNetworkSimulation.GRAVITY = 5;

VoronoiDrip.FluidNetworkSimulation.create = function(spec) {
    var that = {};

    that.pipes = spec.pipes;
    that.gravity = spec.hasOwnProperty('gravity') && spec.gravity ? spec.gravity : VoronoiDrip.FluidNetworkSimulation.GRAVITY;

    that.addFluid = function(pipe, point, volume) {
        that.fluidAdder.add(pipe, point, volume);
    };

    that.start = function() {
        var fns = VoronoiDrip.FluidNetworkSimulation;
        that.metrics = fns.Metrics.create({
            pipes: that.pipes,
            gravity: that.gravity
        });
        that.metrics.start();
        that.overlapSolver = fns.OverlapSolver.create({
            pipes: that.pipes
        });
        that.targetCalculator = fns.TargetCalculator.create({
            pipes: that.pipes,
            metrics: that.metrics
        });
        that.fluidAdder = fns.FluidAdder.create({
            pipes: that.pipes,
            metrics: that.metrics,
            overlapSolver: that.overlapSolver,
            targetCalculator: that.targetCalculator
        });
        that.pressureSolver = fns.PressureSolver.create({
            pipes: that.pipes,
            metrics: that.metrics,
            fluidAdder: that.fluidAdder,
            targetCalculator: that.targetCalculator
        });
        that.fluidMover = fns.FluidMover.create({
            pipes: that.pipes,
            metrics: that.metrics,
            pressureSolver: that.pressureSolver
        });
    };

    that.update = function() {
        that.fluidMover.update();
    };

    return that;
};<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2017 Dan "Ducky" Little
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import React from 'react';
import { Provider, connect } from 'react-redux';
import { Translation } from 'react-i18next';

import { renderFlatTree } from './catalog';
import { isFavorite } from './catalog/layer-favorite';

export class FavoriteLayers extends React.Component {
    render() {
        const is_favorite = (layer) => {
            return isFavorite(this.props.mapSources, layer);
        };

        const favorites = renderFlatTree(this.props.dispatch, this.props.catalog, 'root', is_favorite);

        return (
            <Provider store={this.props.store}>
                <Translation>
                    { t => (

                            {
                                favorites.length > 0 ? '' : (
                                    {t('no-favorites')}
                                )
                            }
                            { favorites }

                    )}
                </Translation>
            </Provider>
        );
    }
}

const mapFavoritesToProps = function(store) {
    return {
        mapSources: store.mapSources,
        catalog: store.catalog
    }
}

export default connect(mapFavoritesToProps)(FavoriteLayers);
<commit_msg>Fix for broken favorites list<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2017 Dan "Ducky" Little
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import React from 'react';
import { Provider, connect } from 'react-redux';
import { Translation } from 'react-i18next';

import { renderFlatTree } from './catalog';
import { isFavorite } from './catalog/layer-favorite';

export class FavoriteLayers extends React.Component {
    render() {
        const is_favorite = (layer) => {
            return isFavorite(this.props.mapSources, layer);
        };

        const favorites = renderFlatTree(this.props.dispatch, this.props.catalog, 'root', this.props.resolution, is_favorite);

        return (
            <Provider store={this.props.store}>
                <Translation>
                    { t => (

                            {
                                favorites.length > 0 ? '' : (
                                    {t('no-favorites')}
                                )
                            }
                            { favorites }

                    )}
                </Translation>
            </Provider>
        );
    }
}

const mapFavoritesToProps = state => ({
    mapSources: state.mapSources,
    catalog: state.catalog,
    resolution: state.map ? state.map.resolution : -1,
});

export default connect(mapFavoritesToProps)(FavoriteLayers);
<|endoftext|>


<commit_before>import {
	DataTexture,
	FloatType,
	UnsignedIntType,
	RGBFormat,
	RGIntegerFormat,
	NearestFilter,
} from 'three';
import {
	FloatVertexAttributeTexture,
	UIntVertexAttributeTexture,
} from './VertexAttributeTexture.js';
import { BYTES_PER_NODE } from '../core/Constants.js';
import {
	BOUNDING_DATA_INDEX,
	COUNT,
	IS_LEAF,
	RIGHT_NODE,
	OFFSET,
	SPLIT_AXIS,
} from '../core/nodeBufferFunctions.js';

function bvhToTextures( bvh, boundsTexture, contentsTexture ) {

	const roots = bvh._roots;

	if ( roots.length !== 1 ) {

		throw new Error( 'MeshBVHUniformStruct: Multi-root BVHs not supported.' );

	}

	const root = roots[ 0 ];
	const uint16Array = new Uint16Array( root );
	const uint32Array = new Uint32Array( root );
	const float32Array = new Float32Array( root );

	// Both bounds need two elements per node so compute the height so it's twice as long as
	// the width so we can expand the row by two and still have a square texture
	const nodeCount = root.byteLength / BYTES_PER_NODE;
	const boundsDimension = 2 * Math.ceil( Math.sqrt( nodeCount / 2 ) );
	const boundsArray = new Float32Array( 3 * boundsDimension * boundsDimension );

	const contentsDimension = Math.ceil( Math.sqrt( nodeCount ) );
	const contentsArray = new Uint32Array( 2 * contentsDimension * contentsDimension );

	for ( let i = 0; i < nodeCount; i ++ ) {

		const nodeIndex32 = i * BYTES_PER_NODE / 4;
		const nodeIndex16 = nodeIndex32 * 2;
		const boundsIndex = BOUNDING_DATA_INDEX( nodeIndex32 );
		for ( let b = 0; b < 6; b ++ ) {

			boundsArray[ 6 * i + b ] = float32Array[ boundsIndex + b ];

		}

		if ( IS_LEAF( nodeIndex16, uint16Array ) ) {

			const count = COUNT( nodeIndex16, uint16Array );
			const offset = OFFSET( nodeIndex32, uint32Array );

			const mergedLeafCount = 0xffff0000 | count;
			contentsArray[ i * 2 + 0 ] = mergedLeafCount;
			contentsArray[ i * 2 + 1 ] = offset;

		} else {

			const rightIndex = 4 * RIGHT_NODE( nodeIndex32, uint32Array ) / BYTES_PER_NODE;
			const splitAxis = SPLIT_AXIS( nodeIndex32, uint32Array );

			contentsArray[ i * 2 + 0 ] = splitAxis;
			contentsArray[ i * 2 + 1 ] = rightIndex;

		}

	}

	boundsTexture.image.data = boundsArray;
	boundsTexture.image.width = boundsDimension;
	boundsTexture.image.height = boundsDimension;
	boundsTexture.format = RGBFormat;
	boundsTexture.type = FloatType;
	boundsTexture.internalFormat = 'RGB32F';
	boundsTexture.minFilter = NearestFilter;
	boundsTexture.magFilter = NearestFilter;
	boundsTexture.generateMipmaps = false;
	boundsTexture.needsUpdate = true;

	contentsTexture.image.data = contentsArray;
	contentsTexture.image.width = contentsDimension;
	contentsTexture.image.height = contentsDimension;
	contentsTexture.format = RGIntegerFormat;
	contentsTexture.type = UnsignedIntType;
	contentsTexture.internalFormat = 'RG32UI';
	contentsTexture.minFilter = NearestFilter;
	contentsTexture.magFilter = NearestFilter;
	contentsTexture.generateMipmaps = false;
	contentsTexture.needsUpdate = true;

}

export class MeshBVHUniformStruct {

	constructor() {

		this.autoDispose = true;
		this.index = new UIntVertexAttributeTexture();
		this.position = new FloatVertexAttributeTexture();
		this.bvhBounds = new DataTexture();
		this.bvhContents = new DataTexture();

		this.index.overrideItemSize = 3;

	}

	updateFrom( bvh ) {

		const { geometry } = bvh;

		bvhToTextures( bvh, this.bvhBounds, this.bvhContents );

		this.index.updateFrom( geometry.index );
		this.position.updateFrom( geometry.attributes.position );

	}

	dispose() {

		const { index, position, bvhBounds, bvhContents } = this;

		if ( index ) index.dispose();
		if ( position ) position.dispose();
		if ( bvhBounds ) bvhBounds.dispose();
		if ( bvhContents ) bvhContents.dispose();

	}

}
<commit_msg>Remove RGBFormat from MeshBVHUniformStruct<commit_after>import {
	DataTexture,
	FloatType,
	UnsignedIntType,
	RGBAFormat,
	RGIntegerFormat,
	NearestFilter,
} from 'three';
import {
	FloatVertexAttributeTexture,
	UIntVertexAttributeTexture,
} from './VertexAttributeTexture.js';
import { BYTES_PER_NODE } from '../core/Constants.js';
import {
	BOUNDING_DATA_INDEX,
	COUNT,
	IS_LEAF,
	RIGHT_NODE,
	OFFSET,
	SPLIT_AXIS,
} from '../core/nodeBufferFunctions.js';

function bvhToTextures( bvh, boundsTexture, contentsTexture ) {

	const roots = bvh._roots;

	if ( roots.length !== 1 ) {

		throw new Error( 'MeshBVHUniformStruct: Multi-root BVHs not supported.' );

	}

	const root = roots[ 0 ];
	const uint16Array = new Uint16Array( root );
	const uint32Array = new Uint32Array( root );
	const float32Array = new Float32Array( root );

	// Both bounds need two elements per node so compute the height so it's twice as long as
	// the width so we can expand the row by two and still have a square texture
	const nodeCount = root.byteLength / BYTES_PER_NODE;
	const boundsDimension = 2 * Math.ceil( Math.sqrt( nodeCount / 2 ) );
	const boundsArray = new Float32Array( 4 * boundsDimension * boundsDimension );

	const contentsDimension = Math.ceil( Math.sqrt( nodeCount ) );
	const contentsArray = new Uint32Array( 2 * contentsDimension * contentsDimension );

	for ( let i = 0; i < nodeCount; i ++ ) {

		const nodeIndex32 = i * BYTES_PER_NODE / 4;
		const nodeIndex16 = nodeIndex32 * 2;
		const boundsIndex = BOUNDING_DATA_INDEX( nodeIndex32 );
		for ( let b = 0; b < 3; b ++ ) {

			boundsArray[ 8 * i + 0 + b ] = float32Array[ boundsIndex + 0 + b ];
			boundsArray[ 8 * i + 4 + b ] = float32Array[ boundsIndex + 3 + b ];

		}

		if ( IS_LEAF( nodeIndex16, uint16Array ) ) {

			const count = COUNT( nodeIndex16, uint16Array );
			const offset = OFFSET( nodeIndex32, uint32Array );

			const mergedLeafCount = 0xffff0000 | count;
			contentsArray[ i * 2 + 0 ] = mergedLeafCount;
			contentsArray[ i * 2 + 1 ] = offset;

		} else {

			const rightIndex = 4 * RIGHT_NODE( nodeIndex32, uint32Array ) / BYTES_PER_NODE;
			const splitAxis = SPLIT_AXIS( nodeIndex32, uint32Array );

			contentsArray[ i * 2 + 0 ] = splitAxis;
			contentsArray[ i * 2 + 1 ] = rightIndex;

		}

	}

	boundsTexture.image.data = boundsArray;
	boundsTexture.image.width = boundsDimension;
	boundsTexture.image.height = boundsDimension;
	boundsTexture.format = RGBAFormat;
	boundsTexture.type = FloatType;
	boundsTexture.internalFormat = 'RGBA32F';
	boundsTexture.minFilter = NearestFilter;
	boundsTexture.magFilter = NearestFilter;
	boundsTexture.generateMipmaps = false;
	boundsTexture.needsUpdate = true;

	contentsTexture.image.data = contentsArray;
	contentsTexture.image.width = contentsDimension;
	contentsTexture.image.height = contentsDimension;
	contentsTexture.format = RGIntegerFormat;
	contentsTexture.type = UnsignedIntType;
	contentsTexture.internalFormat = 'RG32UI';
	contentsTexture.minFilter = NearestFilter;
	contentsTexture.magFilter = NearestFilter;
	contentsTexture.generateMipmaps = false;
	contentsTexture.needsUpdate = true;

}

export class MeshBVHUniformStruct {

	constructor() {

		this.autoDispose = true;
		this.index = new UIntVertexAttributeTexture();
		this.position = new FloatVertexAttributeTexture();
		this.bvhBounds = new DataTexture();
		this.bvhContents = new DataTexture();

		this.index.overrideItemSize = 3;

	}

	updateFrom( bvh ) {

		const { geometry } = bvh;

		bvhToTextures( bvh, this.bvhBounds, this.bvhContents );

		this.index.updateFrom( geometry.index );
		this.position.updateFrom( geometry.attributes.position );

	}

	dispose() {

		const { index, position, bvhBounds, bvhContents } = this;

		if ( index ) index.dispose();
		if ( position ) position.dispose();
		if ( bvhBounds ) bvhBounds.dispose();
		if ( bvhContents ) bvhContents.dispose();

	}

}
<|endoftext|>


<commit_before>import assert from 'assert';
import dateF from 'date-fns';
import fp from 'mostly-func';
import makeDebug from 'debug';
import path from 'path';
import url from 'url';

import { DocTypes, Permissions } from '~/constants';

const debug = makeDebug('playing:content-services:hooks:documentEnrichers');

// check whether there is any folder children
function hasFolderishChild(hook, doc, options) {
  const svcFolders = hook.app.service('folders');
  // only folder need to check hasFolderishChild
  if (doc.type === 'folder') {
    return svcFolders.find({ query: {
      parent: doc.id
    }}).then(result => {
      let folderishChildren = fp.filter(child => {
        return child.metadata.facets && child.metadata.facets.indexOf('Folderish') !== -1;
      }, result.data);
      doc.metadata.hasFolderishChild = folderishChildren.length > 0;
      return doc;
    });
  } else {
    return Promise.resolve(doc);
  }
}

function getBreadcrumbs(hook, doc, options) {
  let breadcrumbs = [];
  let parent = doc.parent;
  while (parent && parent.path) {
    let bread = fp.omit(['parent'], parent);
    breadcrumbs = [bread, ...breadcrumbs];
    parent = parent.parent;
  }
  return breadcrumbs;
}

function getCollections(hook, doc, options) {
  const svcDocuments = hook.app.service('documents');
  const svcUserCollections = hook.app.service('user-collections');
  if (!hook.params.user) return Promise.resolve();

  return svcUserCollections.find({
    query: {
      creator: hook.params.user.id,
      document: doc.id,
      category: 'collection'
    },
    paginate: false
  }).then((results) => {
    if (results) {
      let collections = fp.map(fp.prop('parent'), results);
      if (collections.length > 0) {
        return svcDocuments.find({
          query: { _id: { $in: collections } },
          paginate: false
        });
      }
    }
  }).then((results) => {
    doc.metadata.collections = results || [];
  });
}

function getFavorites(hook, doc, options) {
  const svcUserFavorites = hook.app.service('user-favorites');
  if (!hook.params.user) return Promise.resolve();

  return svcUserFavorites.find({
    query: {
      user: hook.params.user.id,
      document: doc.id
    },
    paginate: false
  }).then((results) => {
    if (results) {
      doc.metadata.favorites = { isFavorite: results.length > 0 };
    }
  });
}

function getPermisionStatus(ace) {
  const now = new Date();
  if (ace.begin || ace.end) {
    if (ace.begin && dateF.isBefore(now, ace.begin)) return 'pending';
    if (ace.begin && ace.end && dateF.isWithinRange(now, ace.start, ace.end)) return 'effective';
    if (ace.end && dateF.isAfter(now, ace.end)) return 'archived';
  }
  return 'effective';
}

function getAces(app, document) {
  const svcDocuments = app.service('documents');
  const svcUserGroups = app.service('user-groups');

  const getDocument = document.title
    ? Promise.resolve(document)
    : svcDocuments.get(document);
  return getDocument.then((doc) => {
    if (!(doc && doc.ACL)) return null;

    let findUsers = Promise.resolve([]);
    let keys = fp.reject(fp.equals('*'), Object.keys(doc.ACL));
    const creators = fp.reject(fp.isNil, fp.map(fp.prop('creator'), Object.values(doc.ACL)));
    keys = keys.concat(creators);
    if (keys.length > 0) {
      findUsers = svcUserGroups.find({ query: {
        _id: { $in: keys }
      }});
    }
    return findUsers.then(users => {
      const aces = fp.map(key => {
        const ace = doc.ACL[key];
        return Object.assign({}, ace, {
          status: getPermisionStatus(ace),
          creator: ace.creator? fp.findById(ace.creator, users) : null,
          user: fp.findById(key, users)
        });
      }, Object.keys(doc.ACL));
      return aces;
    });
  });
}

function getAcls(hook, doc, options) {
  if (!(doc && doc.ACL)) return Promise.resolve(null);

  const inherited = doc.ACL['*'] && doc.ACL['*'].inherited === true;
  const getLocalAces = getAces(hook.app, doc);
  const getInheritedAces = doc.parent? getAces(hook.app, doc.parent) : Promise.resolve(null);

  return Promise.all([getLocalAces, getInheritedAces])
    .then(([localAces, inheritedAces]) => {
      doc.metadata.acls = [];
      // local permissions
      if (localAces) doc.metadata.acls.push({ name: 'local', aces: localAces });
      // inherited permissions
      if (inherited) {
        doc.metadata.acls.push({ name: 'inherited', aces: inheritedAces || []});
      }
    });
}

function getPermission(hook, doc, options) {
  const Types = options.DocTypes || DocTypes;
  const subtypes = Types[doc.type] && Types[doc.type].subtypes;
  // TODO: user acls
  let permissions = ['Everything', 'Read', 'Write', 'ReadWrite'];
  if (subtypes) {
    permissions = permissions.concat(['ReadChildren', 'AddChildren', 'RemoveChildren']);
  }
  return permissions;
}

function getUserVisiblePermissions(hook, doc, options) {
  return ['Read', 'ReadWrite', 'Everything'];
}

function getSubtypes(hook, doc, options) {
  const Types = options.DocTypes || DocTypes;
  const subtypes = Types[doc.type] && Types[doc.type].subtypes;
  if (subtypes) {
    return Object.values(fp.pick(subtypes, Types));
  }
  return [];
}

function getTags(hook, doc, options) {
  return (doc.tags || []);
}

function getThumbnail(hook, doc) {
  const baseUrl = 'bower_components/playing-content-elements/images/icons/';
  return {
    url: url.resolve(baseUrl, doc.type + '.png')
  };
}

// Add document metadata according to request header
export default function documentEnrichers(options = {}) {
  return (hook) => {
    assert(hook.type === 'after', `documentEnrichers must be used as a 'after' hook.`);

    // If no enrichers-document header then skip this hook
    if (!(hook.params.headers && hook.params.headers['enrichers-document'])) {
      debug('Skip documentEnrichers without headers', hook.params);
      return hook;
    }

    let enrichers = hook.params.headers['enrichers-document'].split(',').map(e => e.trim());
    debug('enrichers-document %j', enrichers);

    let results = [].concat(hook.result? hook.result.data || hook.result : []);
    let promises = [];

    results.forEach(doc => {
      doc.metadata = doc.metadata || {};
      enrichers.forEach(enricher => {
        switch(enricher) {
          case 'acls':
            promises.push(getAcls(hook, doc, options));
            break;
          case 'breadcrumb':
            doc.metadata.breadcrumbs = getBreadcrumbs(hook, doc, options);
            break;
          case 'collections':
            promises.push(getCollections(hook, doc, options));
            break;
          case 'favorites':
            promises.push(getFavorites(hook, doc, options));
            break;
          case 'hasFolderishChild':
            promises.push(hasFolderishChild(hook, doc, options));
            break;
          case 'permissions':
            doc.metadata.permissions = getPermission(hook, doc, options);
            break;
          case 'preview':
            break;
          case 'renditions':
            break;
          case 'subtypes':
            doc.metadata.subtypes = getSubtypes(hook, doc, options);
            break;
          case 'tags':
            doc.metadata.tags = getTags(hook, doc, options);
            break;
          case 'thumbnail':
            doc.metadata.thumbnail = getThumbnail(hook, doc, options);
            break;
          case 'userVisiblePermissions':
            doc.metadata.userVisiblePermissions = getUserVisiblePermissions(hook, doc, options);
            break;
        }
      });
    });

    return Promise.all(promises).then(results => hook);
  };
}
<commit_msg>Fix get hook.result.data<commit_after>import assert from 'assert';
import dateF from 'date-fns';
import fp from 'mostly-func';
import makeDebug from 'debug';
import path from 'path';
import url from 'url';

import { DocTypes, Permissions } from '~/constants';

const debug = makeDebug('playing:content-services:hooks:documentEnrichers');

// check whether there is any folder children
function hasFolderishChild(hook, doc, options) {
  const svcFolders = hook.app.service('folders');
  // only folder need to check hasFolderishChild
  if (doc.type === 'folder') {
    return svcFolders.find({ query: {
      parent: doc.id
    }}).then(result => {
      let folderishChildren = fp.filter(child => {
        return child.metadata.facets && child.metadata.facets.indexOf('Folderish') !== -1;
      }, result.data);
      doc.metadata.hasFolderishChild = folderishChildren.length > 0;
      return doc;
    });
  } else {
    return Promise.resolve(doc);
  }
}

function getBreadcrumbs(hook, doc, options) {
  let breadcrumbs = [];
  let parent = doc.parent;
  while (parent && parent.path) {
    let bread = fp.omit(['parent'], parent);
    breadcrumbs = [bread, ...breadcrumbs];
    parent = parent.parent;
  }
  return breadcrumbs;
}

function getCollections(hook, doc, options) {
  const svcDocuments = hook.app.service('documents');
  const svcUserCollections = hook.app.service('user-collections');
  if (!hook.params.user) return Promise.resolve();

  return svcUserCollections.find({
    query: {
      creator: hook.params.user.id,
      document: doc.id,
      category: 'collection'
    },
    paginate: false
  }).then((results) => {
    if (results) {
      let collections = fp.map(fp.prop('parent'), results);
      if (collections.length > 0) {
        return svcDocuments.find({
          query: { _id: { $in: collections } },
          paginate: false
        });
      }
    }
  }).then((results) => {
    doc.metadata.collections = results || [];
  });
}

function getFavorites(hook, doc, options) {
  const svcUserFavorites = hook.app.service('user-favorites');
  if (!hook.params.user) return Promise.resolve();

  return svcUserFavorites.find({
    query: {
      user: hook.params.user.id,
      document: doc.id
    },
    paginate: false
  }).then((results) => {
    if (results) {
      doc.metadata.favorites = { isFavorite: results.length > 0 };
    }
  });
}

function getPermisionStatus(ace) {
  const now = new Date();
  if (ace.begin || ace.end) {
    if (ace.begin && dateF.isBefore(now, ace.begin)) return 'pending';
    if (ace.begin && ace.end && dateF.isWithinRange(now, ace.start, ace.end)) return 'effective';
    if (ace.end && dateF.isAfter(now, ace.end)) return 'archived';
  }
  return 'effective';
}

function getAces(app, document) {
  const svcDocuments = app.service('documents');
  const svcUserGroups = app.service('user-groups');

  const getDocument = document.title
    ? Promise.resolve(document)
    : svcDocuments.get(document);
  return getDocument.then((doc) => {
    if (!(doc && doc.ACL)) return null;

    let findUsers = Promise.resolve([]);
    let keys = fp.reject(fp.equals('*'), Object.keys(doc.ACL));
    const creators = fp.reject(fp.isNil, fp.map(fp.prop('creator'), Object.values(doc.ACL)));
    keys = keys.concat(creators);
    if (keys.length > 0) {
      findUsers = svcUserGroups.find({ query: {
        _id: { $in: keys }
      }});
    }
    return findUsers.then(users => {
      const aces = fp.map(key => {
        const ace = doc.ACL[key];
        return Object.assign({}, ace, {
          status: getPermisionStatus(ace),
          creator: ace.creator? fp.findById(ace.creator, users) : null,
          user: fp.findById(key, users)
        });
      }, Object.keys(doc.ACL));
      return aces;
    });
  });
}

function getAcls(hook, doc, options) {
  if (!(doc && doc.ACL)) return Promise.resolve(null);

  const inherited = doc.ACL['*'] && doc.ACL['*'].inherited === true;
  const getLocalAces = getAces(hook.app, doc);
  const getInheritedAces = doc.parent? getAces(hook.app, doc.parent) : Promise.resolve(null);

  return Promise.all([getLocalAces, getInheritedAces])
    .then(([localAces, inheritedAces]) => {
      doc.metadata.acls = [];
      // local permissions
      if (localAces) doc.metadata.acls.push({ name: 'local', aces: localAces });
      // inherited permissions
      if (inherited) {
        doc.metadata.acls.push({ name: 'inherited', aces: inheritedAces || []});
      }
    });
}

function getPermission(hook, doc, options) {
  const Types = options.DocTypes || DocTypes;
  const subtypes = Types[doc.type] && Types[doc.type].subtypes;
  // TODO: user acls
  let permissions = ['Everything', 'Read', 'Write', 'ReadWrite'];
  if (subtypes) {
    permissions = permissions.concat(['ReadChildren', 'AddChildren', 'RemoveChildren']);
  }
  return permissions;
}

function getUserVisiblePermissions(hook, doc, options) {
  return ['Read', 'ReadWrite', 'Everything'];
}

function getSubtypes(hook, doc, options) {
  const Types = options.DocTypes || DocTypes;
  const subtypes = Types[doc.type] && Types[doc.type].subtypes;
  if (subtypes) {
    return Object.values(fp.pick(subtypes, Types));
  }
  return [];
}

function getTags(hook, doc, options) {
  return (doc.tags || []);
}

function getThumbnail(hook, doc) {
  const baseUrl = 'bower_components/playing-content-elements/images/icons/';
  return {
    url: url.resolve(baseUrl, doc.type + '.png')
  };
}

// Add document metadata according to request header
export default function documentEnrichers(options = {}) {
  return (hook) => {
    assert(hook.type === 'after', `documentEnrichers must be used as a 'after' hook.`);

    // If no enrichers-document header then skip this hook
    if (!(hook.params.headers && hook.params.headers['enrichers-document'])) {
      debug('Skip documentEnrichers without headers', hook.params);
      return hook;
    }

    let enrichers = hook.params.headers['enrichers-document'].split(',').map(e => e.trim());
    debug('enrichers-document %j', enrichers);

    let results = [].concat(hook.result && hook.result.data || hook.result || []);
    let promises = [];

    results.forEach(doc => {
      doc.metadata = doc.metadata || {};
      enrichers.forEach(enricher => {
        switch(enricher) {
          case 'acls':
            promises.push(getAcls(hook, doc, options));
            break;
          case 'breadcrumb':
            doc.metadata.breadcrumbs = getBreadcrumbs(hook, doc, options);
            break;
          case 'collections':
            promises.push(getCollections(hook, doc, options));
            break;
          case 'favorites':
            promises.push(getFavorites(hook, doc, options));
            break;
          case 'hasFolderishChild':
            promises.push(hasFolderishChild(hook, doc, options));
            break;
          case 'permissions':
            doc.metadata.permissions = getPermission(hook, doc, options);
            break;
          case 'preview':
            break;
          case 'renditions':
            break;
          case 'subtypes':
            doc.metadata.subtypes = getSubtypes(hook, doc, options);
            break;
          case 'tags':
            doc.metadata.tags = getTags(hook, doc, options);
            break;
          case 'thumbnail':
            doc.metadata.thumbnail = getThumbnail(hook, doc, options);
            break;
          case 'userVisiblePermissions':
            doc.metadata.userVisiblePermissions = getUserVisiblePermissions(hook, doc, options);
            break;
        }
      });
    });

    return Promise.all(promises).then(results => hook);
  };
}
<|endoftext|>


<commit_before>// @flow

import 'babel-polyfill';
import fetch from 'isomorphic-fetch';
import db from '../connection';

if (process.argv.length < 5) {
  console.log('Usage: node import-measurable.js year position measurable [--commit]');
  process.exit(1);
}

const combineYear: number = Number(process.argv[2]);
const nflPosition: string = process.argv[3];
const measurable: string = process.argv[4];
const isDryRun: boolean = process.argv[5] !== '--commit';

const validPositions = ['QB', 'RB', 'WR', 'TE', 'OL', 'DL', 'LB', 'CB', 'S', 'SPECIALISTS'];

if (!validPositions.includes(nflPosition)) {
  console.log(`Invalid position. Please select one of the following:
    ${JSON.stringify(validPositions)}`);
  process.exit(1);
}

if (!combineYear || combineYear > 2100 || combineYear < 2000) {
  console.log(`Invalid combine year: ${combineYear}. Please provide a valid year number.`);
  process.exit(1);
}

const validMeasurables = {
  BENCH_PRESS: 9,
  FORTY_YARD_DASH: 8,
  VERTICAL_JUMP: 10,
  BROAD_JUMP: 11,
  THREE_CONE_DRILL: 12,
  TWENTY_YARD_SHUTTLE: 13,
  SIXTY_YARD_SHUTTLE: 14,
};

if (!Object.keys(validMeasurables).includes(measurable)) {
  console.log(`Invalid measurable. Please select one of the following:
    ${JSON.stringify(validMeasurables)}`);
  process.exit(1);
}

const getSchoolId = async (nflCollegeName: string) => {
  let schoolName = nflCollegeName.replace(' St.', ' State');
  if (schoolName === 'Central Florida') {
    schoolName = 'UCF';
  } else if (schoolName === 'Mississippi') {
    schoolName = 'Ole Miss';
  } else if (schoolName === 'N.C. State') {
    schoolName = 'NC State';
  }
  const school: { id: number } = await db.oneOrNone(
    'select id from t_school where name = $(name)',
    { name: schoolName },
  );
  if (!school) {
    return null;
  }
  return school.id;
};

const insertMeasurement = async (playerId, measurableKey, measurement) =>
  isDryRun
    ? (() => {
      console.log(`Measurement: (${playerId}, ${measurableKey}, ${measurement})`);
    })()
    : db.none(
      `insert into t_measurement (status, player_id, measurable_id, measurement, source)
        values (0, $(playerId), $(measurableKey), $(measurement), 1)`,
      { playerId, measurableKey, measurement },
    );

const doImport = async () => {
  let nflData = [];
  try {
    nflData = (await (await fetch(
      `http://www.nfl.com/liveupdate/combine/${combineYear}/${measurable}/${nflPosition}.json`,
    )).json()).data;
  } catch (e) {
    console.log(e.message);
    process.exit(1);
  }

  await Promise.all(nflData.map(async (player) => {
    if (player.result === null) {
      console.log(`Null result: skipping for ${player.firstName} ${player.lastName}`);
      return false;
    }

    const schoolId = await getSchoolId(player.college);
    if (!schoolId) {
      console.log(`Unable to find player school: ${JSON.stringify(player)}`);
      return false;
    }

    try {
      const dbPlayer = await db.one(
        `select id from t_player
          where first_name = $(firstName)
            and last_name = $(lastName)
            and draft_year = $(combineYear)
            and school_id = $(schoolId)`,
        {
          firstName: player.firstName,
          lastName: player.lastName,
          combineYear,
          schoolId,
        },
      );

      const measurableKey = validMeasurables[measurable];

      await insertMeasurement(dbPlayer.id, measurableKey, player.result);
    } catch (e) {
      console.log(`Failed to find player: ${e.message} ${JSON.stringify(player)}`);
      return false;
    }
    return true;
  }));
};

doImport().then(() => { console.log('Done with import'); process.exit(0); });
<commit_msg>Making sure I don't dupe data on import<commit_after>// @flow

import 'babel-polyfill';
import fetch from 'isomorphic-fetch';
import db from '../connection';

if (process.argv.length < 5) {
  console.log('Usage: node import-measurable.js year position measurable [--commit]');
  process.exit(1);
}

const combineYear: number = Number(process.argv[2]);
const nflPosition: string = process.argv[3];
const measurable: string = process.argv[4];
const isDryRun: boolean = process.argv[5] !== '--commit';

const validPositions = ['QB', 'RB', 'WR', 'TE', 'OL', 'DL', 'LB', 'CB', 'S', 'SPECIALISTS'];

if (!validPositions.includes(nflPosition)) {
  console.log(`Invalid position. Please select one of the following:
    ${JSON.stringify(validPositions)}`);
  process.exit(1);
}

if (!combineYear || combineYear > 2100 || combineYear < 2000) {
  console.log(`Invalid combine year: ${combineYear}. Please provide a valid year number.`);
  process.exit(1);
}

const validMeasurables = {
  BENCH_PRESS: 9,
  FORTY_YARD_DASH: 8,
  VERTICAL_JUMP: 10,
  BROAD_JUMP: 11,
  THREE_CONE_DRILL: 12,
  TWENTY_YARD_SHUTTLE: 13,
  SIXTY_YARD_SHUTTLE: 14,
};

if (!Object.keys(validMeasurables).includes(measurable)) {
  console.log(`Invalid measurable. Please select one of the following:
    ${JSON.stringify(validMeasurables)}`);
  process.exit(1);
}

const getSchoolId = async (nflCollegeName: string) => {
  let schoolName = nflCollegeName.replace(' St.', ' State');
  if (schoolName === 'Central Florida') {
    schoolName = 'UCF';
  } else if (schoolName === 'Mississippi') {
    schoolName = 'Ole Miss';
  } else if (schoolName === 'N.C. State') {
    schoolName = 'NC State';
  }
  const school: { id: number } = await db.oneOrNone(
    'select id from t_school where name = $(name)',
    { name: schoolName },
  );
  if (!school) {
    return null;
  }
  return school.id;
};

const insertMeasurement = async (playerId, measurableKey, measurement) =>
  isDryRun
    ? (() => {
      console.log(`Measurement: (${playerId}, ${measurableKey}, ${measurement})`);
    })()
    : db.none(
      `insert into t_measurement (status, player_id, measurable_id, measurement, source)
        values (0, $(playerId), $(measurableKey), $(measurement), 1)`,
      { playerId, measurableKey, measurement },
    );

const doImport = async () => {
  let nflData = [];
  try {
    nflData = (await (await fetch(
      `http://www.nfl.com/liveupdate/combine/${combineYear}/${measurable}/${nflPosition}.json`,
    )).json()).data;
  } catch (e) {
    console.log(e.message);
    process.exit(1);
  }

  await Promise.all(nflData.map(async (player) => {
    if (player.result === null) {
      console.log(`Null result: skipping for ${player.firstName} ${player.lastName}`);
      return false;
    }

    const schoolId = await getSchoolId(player.college);
    if (!schoolId) {
      console.log(`Unable to find player school: ${JSON.stringify(player)}`);
      return false;
    }

    try {
      const dbPlayer = await db.one(
        `select id from t_player
          where first_name = $(firstName)
            and last_name = $(lastName)
            and draft_year = $(combineYear)
            and school_id = $(schoolId)`,
        {
          firstName: player.firstName,
          lastName: player.lastName,
          combineYear,
          schoolId,
        },
      );

      const measurableKey = validMeasurables[measurable];

      const alreadyImported = !!await db.oneOrNone(
        `select id
          from t_measurement
          where player_id=$(playerId)
            and measurable_id=$(measurableKey)
            and measurement=$(measurement)
            and source=1`,
        { playerId: dbPlayer.id, measurableKey, measurement: player.result },
      );

      if (!alreadyImported) {
        await insertMeasurement(dbPlayer.id, measurableKey, player.result);
      } else {
        console.log(`Already imported ${measurable} for ${player.firstName} ${player.lastName}`);
      }
    } catch (e) {
      console.log(`Failed to find player: ${e.message} ${JSON.stringify(player)}`);
      return false;
    }
    return true;
  }));
};

doImport().then(() => { console.log('Done with import'); process.exit(0); });
<|endoftext|>


<commit_before>Ext4.define('Kwf.Ext4.Controller.Form', {
    mixins: {
        observable: 'Ext.util.Observable'
    },
    autoLoadComboBoxStores: true,

    form: null,

    constructor: function(config) {
        this.mixins.observable.constructor.call(this, config);
        this.init();
    },

    init: function()
    {
        if (!this.form) Ext4.Error.raise('form config is required');
        if (!this.form instanceof Ext4.form.Panel) Ext4.Error.raise('form config needs to be a Ext.form.Panel');

        var form = this.form;
        if (typeof this.saveButton == 'undefined') this.saveButton = form.down('button#save');
        if (this.saveButton && !this.saveButton instanceof Ext4.button.Button) Ext4.Error.raise('saveButton config needs to be a Ext.button.Button');
        if (this.saveButton) {
            this.saveButton.on('click', this.onSaveClick, this);
        }
    },

    load: function(row)
    {
        if (this.autoLoadComboBoxStores) {
            Ext4.each(this.form.query("combobox"), function(i) {
                if (i.queryMode == 'remote' && i.store && !i.store.lastOptions) {
                    i.store.load();
                }
            }, this);
            Ext4.each(this.form.query('multiselectfield'), function(i) {
                if (i.store && !i.store.lastOptions) {
                    i.store.load();
                }
            }, this);
        }

        //when loading the same row (by comparing the id) keep dirty values
        var keepDirtyValues = this.form.getForm()._record
            && this.form.getForm()._record.getId() == row.getId();

        this.form.getForm()._record = row;

        // Suspend here because setting the value on a field could trigger
        // a layout, for example if an error gets set, or it's a display field
        Ext4.suspendLayouts();
        Ext4.iterate(row.getData(), function(fieldId, val) {
            var field = this.form.getForm().findField(fieldId);
            if (field) {
                if (keepDirtyValues && field.isDirty()) {
                    return;
                }
                field.setValue(val);
                field.resetOriginalValue();
            }
        }, this);
        Ext4.resumeLayouts(true);
    },

    onSaveClick: function()
    {
        if (!this.form.getForm().isValid()) {
            Ext4.Msg.alert(trlKwf('Save'),
                trlKwf("Can't save, please fill all red underlined fields correctly."));
            return false;
        }

        this.save();

        this.getLoadedRecord().save({
            success: function() {
                this.fireEvent('savesuccess', this.getLoadedRecord());
                this.load(this.getLoadedRecord());
            },
            scope: this
        });
    },

    save: function()
    {
        if (!this.form.getRecord()) return;

        this.form.updateRecord();

        //trackResetOnLoad only calls resetOriginalValue on load, not on updateRecord
        Ext4.each(this.form.getRecord().fields.items, function(field) {
            var f = this.form.getForm().findField(field.name);
            if (f) {
                f.resetOriginalValue();
            }
        }, this);
    },

    getLoadedRecord: function()
    {
        return this.form.getRecord();
    }
});
<commit_msg>Ext4 Form: rename method and add options with callback<commit_after>Ext4.define('Kwf.Ext4.Controller.Form', {
    mixins: {
        observable: 'Ext.util.Observable'
    },
    autoLoadComboBoxStores: true,

    form: null,

    constructor: function(config) {
        this.mixins.observable.constructor.call(this, config);
        this.init();
    },

    init: function()
    {
        if (!this.form) Ext4.Error.raise('form config is required');
        if (!this.form instanceof Ext4.form.Panel) Ext4.Error.raise('form config needs to be a Ext.form.Panel');

        var form = this.form;
        if (typeof this.saveButton == 'undefined') this.saveButton = form.down('button#save');
        if (this.saveButton && !this.saveButton instanceof Ext4.button.Button) Ext4.Error.raise('saveButton config needs to be a Ext.button.Button');
        if (this.saveButton) {
            this.saveButton.on('click', this.validateAndSubmit, this);
        }
    },

    load: function(row)
    {
        if (this.autoLoadComboBoxStores) {
            Ext4.each(this.form.query("combobox"), function(i) {
                if (i.queryMode == 'remote' && i.store && !i.store.lastOptions) {
                    i.store.load();
                }
            }, this);
            Ext4.each(this.form.query('multiselectfield'), function(i) {
                if (i.store && !i.store.lastOptions) {
                    i.store.load();
                }
            }, this);
        }

        //when loading the same row (by comparing the id) keep dirty values
        var keepDirtyValues = this.form.getForm()._record
            && this.form.getForm()._record.getId() == row.getId();

        this.form.getForm()._record = row;

        // Suspend here because setting the value on a field could trigger
        // a layout, for example if an error gets set, or it's a display field
        Ext4.suspendLayouts();
        Ext4.iterate(row.getData(), function(fieldId, val) {
            var field = this.form.getForm().findField(fieldId);
            if (field) {
                if (keepDirtyValues && field.isDirty()) {
                    return;
                }
                field.setValue(val);
                field.resetOriginalValue();
            }
        }, this);
        Ext4.resumeLayouts(true);
    },

    validateAndSubmit: function(options)
    {
        if (!this.form.getForm().isValid()) {
            Ext4.Msg.alert(trlKwf('Save'),
                trlKwf("Can't save, please fill all red underlined fields correctly."));
            return false;
        }

        this.save();

        this.getLoadedRecord().save({
            success: function() {
                this.fireEvent('savesuccess', this.getLoadedRecord());
                this.load(this.getLoadedRecord());
                if (options.success) {
                    options.success.call(options.scope || this);
                }
            },
            failure: function() {
                if (options.failure) {
                    options.failure.call(options.scope || this);
                }
            },
            scope: this
        });
        return true;
    },

    save: function()
    {
        if (!this.form.getRecord()) return;

        this.form.updateRecord();

        //trackResetOnLoad only calls resetOriginalValue on load, not on updateRecord
        Ext4.each(this.form.getRecord().fields.items, function(field) {
            var f = this.form.getForm().findField(field.name);
            if (f) {
                f.resetOriginalValue();
            }
        }, this);
    },

    getLoadedRecord: function()
    {
        return this.form.getRecord();
    }
});
<|endoftext|>