<commit_before>import Ember from 'ember';
import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers';
import { defaultFieldDataTypeClassification, classifiers, datasetClassifiers } from 'wherehows-web/constants';

const {
  get,
  set,
  isBlank,
  isPresent,
  computed,
  getWithDefault,
  setProperties,
  Component,
  String: { htmlSafe }
} = Ember;

/**
 * String constant identifying the classified fields on the security spec
 * @type {String}
 */
const sourceClassificationKey = 'securitySpecification.classification';

/**
 * String constant identifying the datasetClassification on the security spec
 * @type {String}
 */
const datasetClassificationKey = 'securitySpecification.datasetClassification';

/**
 * List of logical types  / field level data types
 * https://iwww.corp.linkedin.com/wiki/cf/display/DWH/List+of+Metadata+for+Data+Sets
 * @type {Array}
 */
const logicalTypes = Object.keys(defaultFieldDataTypeClassification);
/**
 * A list of available keys for the datasetClassification map on the security specification
 * @type {Array}
 */
const datasetClassifiersKeys = Object.keys(datasetClassifiers);

// TODO: DSS-6671 Extract to constants module
const successUpdating = 'Your changes have been successfully saved!';
const failedUpdating = 'Oops! We are having trouble updating this dataset at the moment.';
const missingTypes = 'Looks like some fields are marked as `Confidential` or ' +
  '`Highly Confidential` but do not have a specified `Field Format`?';
const hiddenTrackingFieldsMsg = htmlSafe(
  '
Hey! Just a heads up that some fields in this dataset have been hidden from the table(s) below. ' +
  'These are tracking fields for which we\'ve been able to predetermine the compliance classification.
' +
  '
For example: header.memberId, requestHeader. ' +
  'Hopefully, this saves you some scrolling!
'
);

/**
 * Takes a string, returns a formatted string. Niche , single use case
 * for now, so no need to make into a helper
 * @param {String} string
 */
const formatAsCapitalizedStringWithSpaces = string =>
  string.replace(/[A-Z]/g, match => ` ${match}`).capitalize();

export default Component.extend({
  sortColumnWithName: 'identifierField',
  filterBy: 'identifierField',
  sortDirection: 'asc',
  searchTerm: '',
  hiddenTrackingFields: hiddenTrackingFieldsMsg,

  // Map classifiers to options better consumed by  drop down
  classifiers: classifiers.map(value => ({
    value,
    label: formatAsCapitalizedStringWithSpaces(value)
  })),

  // Map logicalTypes to options better consumed by  drop down
  logicalTypes: ['', ...logicalTypes.sort()].map(value => {
    const label = value ?
      value.replace(/_/g, ' ')
        .replace(/([A-Z]{3,})/g, f => f.toLowerCase().capitalize()) :
      'Not Specified';

    return {
      value,
      label
    };
  }),

  /**
   * Checks that all tags/ dataset content types have a boolean value
   * @type {Ember.computed}
   */
  isDatasetFullyClassified: computed('datasetClassification', function() {
    const datasetClassification = get(this, 'datasetClassification');

    return Object.keys(datasetClassification)
      .map(key => ({ value: datasetClassification[key].value }))
      .every(({ value }) => [true, false].includes(value));
  }),

  /**
   * Computed property that is dependent on all the keys in the datasetClassification map
   *   Returns a new map of datasetClassificationKey: String-> Object.<Boolean|undefined,String>
   * @type {Ember.computed}
   */
  datasetClassification: computed(`${datasetClassificationKey}.{${datasetClassifiersKeys.join(',')}}`, function() {
    const sourceDatasetClassification = get(this, datasetClassificationKey) || {};

    return Object.keys(datasetClassifiers).reduce((datasetClassification, classifier) => {
      datasetClassification[classifier] = {
        value: sourceDatasetClassification[classifier],
        label: datasetClassifiers[classifier]
      };

      return datasetClassification;
    }, {});
  }),

  /**
   * Creates a lookup table of fieldNames to classification
   *   Also, the expectation is that the association from fieldName -> classification
   *   is one-to-one hence no check to ensure a fieldName gets clobbered
   *   in the lookup assignment
   */
  fieldNameToClass: computed(
    `${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
    function () {
      const sourceClasses = getWithDefault(this, sourceClassificationKey, []);
      // Creates a lookup table of fieldNames to classification
      //   Also, the expectation is that the association from fieldName -> classification
      //   is one-to-one hence no check to ensure a fieldName gets clobbered
      //   in the lookup assignment
      return Object.keys(sourceClasses)
        .reduce((lookup, classificationKey) =>
            // For the provided classificationKey, iterate over it's fieldNames,
            //   and assign the classificationKey to the fieldName in the table
            (sourceClasses[classificationKey] || []).reduce((lookup, field) => {
              const { identifierField } = field;
              // cKey -> 1...fieldNameList => fieldName -> cKey
              lookup[identifierField] = classificationKey;
              return lookup;
            }, lookup),
          {}
        );
    }),

  /**
   * Lists all the dataset fields found in the `columns` api, and intersects
   *   each with the currently classified field names in
   *   securitySpecification.classification or null if not found
   */
  classificationDataFields: computed(
    `${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
    'schemaFieldNamesMappedToDataTypes',
    function () {
      // Set default or if already in policy, retrieve current values from
      //   privacyCompliancePolicy.compliancePurgeEntities
      return getWithDefault(
        this,
        'schemaFieldNamesMappedToDataTypes',
        []
      ).map(({ fieldName: identifierField, identifierType }) => {
        // Get the current classification list
        const currentClassLookup = get(this, 'fieldNameToClass');
        const classification = currentClassLookup[identifierField];

        // If the classification type exists, then find the identifierField, and
        //   assign to field, otherwise null
        //   Rather than assigning the default classification here, nulling gives the benefit of allowing
        //   subsequent consumer know that this field did not have a previous classification
        const field = classification ?
          get(this, `${sourceClassificationKey}.${classification}`)
            .findBy('identifierField', identifierField) :
          null;

        // Extract the logicalType from the field
        const logicalType = isPresent(field) ? field.logicalType : null;

        // Map to a new literal containing these props
        return {
          identifierType,
          identifierField,
          classification,
          logicalType
        };
      });
    }
  ),

  /**
   * @type {Boolean} cached boolean flag indicating that fields do contain a `kafka type`
   *    tracking header.
   *    Used to indicate to viewer that these fields are hidden.
   */
  containsHiddenTrackingFields: computed(
    'classificationDataFieldsSansHiddenTracking.length',
    function () {
      // If their is a diff in complianceDataFields and complianceDataFieldsSansHiddenTracking,
      //   then we have hidden tracking fields
      return get(this, 'classificationDataFieldsSansHiddenTracking.length') !== get(this, 'classificationDataFields.length');
    }),

  /**
   * @type {Array.<Object>} Filters the mapped confidential data fields without `kafka type`
   *   tracking headers
   */
  classificationDataFieldsSansHiddenTracking: computed('classificationDataFields.[]', function () {
    return get(this, 'classificationDataFields')
      .filter(({ identifierField }) => !isTrackingHeaderField(identifierField));
  }),

  /**
   * TODO: DSS-6672 Extract to notifications service
   * Helper method to update user when an async server update to the
   * security specification is handled.
   * @param {XMLHttpRequest|Promise|jqXHR|*} request the server request
   * @param {String} [successMessage] optional _message for successful response
   */
  whenRequestCompletes(request, { successMessage } = {}) {
    Promise.resolve(request)
      .then(({ status = 'error' }) => {
        // The server api currently responds with an object containing
        //   a status when complete
        return status === 'ok' ?
          setProperties(this, {
            _message: successMessage || successUpdating,
            _alertType: 'success'
          }) :
          Promise.reject(new Error(`Reason code for this is ${status}`));
      })
      .catch(err => {
        let _message = `${failedUpdating} \n ${err}`;
        let _alertType = 'danger';

        if (get(this, 'isNewSecuritySpecification')) {
          _message = 'This dataset does not have any ' +
            'previously saved fields with a Security Classification.';
          _alertType = 'info';
        }

        setProperties(this, {
          _message,
          _alertType
        });
      });
  },

  /**
   * TODO:DSS-6719 refactor into mixin
   * Clears recently shown user messages
   */
  clearMessages() {
    return setProperties(this, {
      _message: '',
      _alertType: ''
    });
  },

  /**
   * Takes an identifierField and a logicalType and updates the field on the
   * classification if it exists. Otherwise this is a no-op
   * @param {String} identifierField
   * @param {String} logicalType the type to be updated
   */
  changeFieldLogicalType(identifierField, logicalType) {
    const nextProps = { identifierField, logicalType };
    // The current classification name for the candidate identifier
    const currentClassLookup = get(this, 'fieldNameToClass');
    const defaultClassification = getWithDefault(this, `${sourceClassificationKey}.${defaultFieldDataTypeClassification[logicalType]}`, []);
    let currentClassificationName = currentClassLookup[identifierField];

    /**
     * If the field does not already have a classification, we set it's default
     * @link classificationDataFields
     */
    if (isBlank(currentClassificationName)) {
      currentClassificationName = defaultFieldDataTypeClassification[logicalType];
    }
    // The current classification list
    const currentClassification = get(this, `${sourceClassificationKey}.${currentClassificationName}`);

    if (!Array.isArray(currentClassification)) {
      throw new Error(`
      You have specified a classification object that is not a list ${currentClassification}.
      Ensure that the classification for this identifierField (${identifierField}) is
      set before attempting to change the logicalType.
      `);
    }

    const field = currentClassification.findBy('identifierField', identifierField);

    // Since this is change on the logical type, if the field already exists in a classification
    //   list, we remove it to allow applying the default for the new field type, below
    if (isPresent(field)) {
      // Remove identifierField from list
      currentClassification.setObjects(
        currentClassification.filter(
          ({ identifierField: fieldName }) => fieldName !== identifierField)
      );
    }

    // Update the default classification list
    return defaultClassification.setObjects([nextProps, ...defaultClassification]);
  },

  /**
   * Checks that each field in a given source classification has a value
   *   for the logicalType
   * @param {Array} sourceClassification
   */
  ensureFieldsContainLogicalType: (sourceClassification = []) =>
    sourceClassification.every(({ logicalType }) => logicalType),

  actions: {
    /**
     * Updates the logical type for a field with the provided identifierField
     * @param {String} identifierField the name of the field to update
     * @param {String} logicalType the updated logical type
     * @return {*|String|void}
     */
    updateLogicalType({ identifierField }, { value: logicalType }) {
      // TODO:DSS-6719 refactor into mixin
      this.clearMessages();
      return this.changeFieldLogicalType(identifierField, logicalType);
    },
    /**
     * Toggles the provided identifierField onto a classification list
     *   on securitySpecification.classification, identified by the provided
     *   classKey.
     * @param {Object} props field attributes for the field to update on the classification list
     * @param {String} classKey the name of the class to add, or potentially
     *   remove the identifierField from
     */
    updateClassification(props, { value: classKey }) {
      const { identifierField } = props;
      // fieldNames can be paths i.e. identifierField.identifierPath.subPath
      //   therefore, using Ember's `path lookup` syntax will not work
      const currentClassLookup = get(this, 'fieldNameToClass');
      const currentClass = currentClassLookup[identifierField];

      // TODO:DSS-6719 refactor into mixin
      this.clearMessages();
      // Since the association from identifierField -> classification is 1-to-1
      //  ensure that we do not currently have this identifierField
      // in any other classification lists by checking that the lookup is void
      if (!isBlank(currentClass)) {
        // Get the current classification list
        const currentClassification = get(
          this,
          `${sourceClassificationKey}.${currentClass}`
        );

        // Remove identifierField from list
        currentClassification.setObjects(
          currentClassification.filter(
            ({ identifierField: fieldName }) => fieldName !== identifierField)
        );
      }

      if (classKey) {
        // Get the candidate list
        let classification = get(
          this,
          `${sourceClassificationKey}.${classKey}`
        );
        // In the case that the list is not pre-populated,
        //  the value will be the default null, array ops won't work here
        //  ...so make array
        if (!classification) {
          classification = set(this, `${sourceClassificationKey}.${classKey}`, []);
        }

        // Finally perform operation
        classification.addObject(Object.assign({}, props));
      }
    },

    /**
     * Updates the source object representing the current datasetClassification map
     * @param {String} classifier the property on the datasetClassification to update
     * @param {Boolean} value flag indicating if this dataset contains member data for the specified classifier
     */
    didChangeDatasetClassification(classifier, value) {
      let sourceDatasetClassification = getWithDefault(this, datasetClassificationKey, {});

      // For datasets initially without a datasetClassification, the default value is null
      if (sourceDatasetClassification === null) {
        sourceDatasetClassification = set(this, datasetClassificationKey, {});
      }

      return set(sourceDatasetClassification, classifier, value);
    },

    /**
     * Notify controller to propagate changes
     * @return {Boolean}
     */
    saveSecuritySpecification() {
      /**
       * For Each classifier ensure that the fields on the securitySpec
       *   contain a valid `logicalType` attribute
       * @type {Boolean}
       */
      const classedFieldsHaveLogicalType = classifiers.every(classifier =>
        this.ensureFieldsContainLogicalType(
          getWithDefault(this, `${sourceClassificationKey}.${classifier}`, [])
        ));

      if (classedFieldsHaveLogicalType) {
        this.whenRequestCompletes(get(this, 'onSave')());
      } else {
        setProperties(this, {
          _message: missingTypes,
          _alertType: 'danger'
        });
      }

      return false;
    },

    /**
     * Rolls back changes made to the compliance spec to current
     * server state
     */
    resetSecuritySpecification() {
      const options = {
        successMessage: 'Field classification has been reset to the previously saved state.'
      };
      this.whenRequestCompletes(get(this, 'onReset')(), options);
    }
  }
});
<commit_msg>fixes superfluous classification field addition from ui<commit_after>import Ember from 'ember';
import isTrackingHeaderField from 'wherehows-web/utils/validators/tracking-headers';
import { defaultFieldDataTypeClassification, classifiers, datasetClassifiers } from 'wherehows-web/constants';

const {
  get,
  set,
  isBlank,
  isPresent,
  computed,
  getWithDefault,
  setProperties,
  Component,
  String: { htmlSafe }
} = Ember;

/**
 * String constant identifying the classified fields on the security spec
 * @type {String}
 */
const sourceClassificationKey = 'securitySpecification.classification';

/**
 * String constant identifying the datasetClassification on the security spec
 * @type {String}
 */
const datasetClassificationKey = 'securitySpecification.datasetClassification';

/**
 * List of logical types  / field level data types
 * https://iwww.corp.linkedin.com/wiki/cf/display/DWH/List+of+Metadata+for+Data+Sets
 * @type {Array}
 */
const logicalTypes = Object.keys(defaultFieldDataTypeClassification);
/**
 * A list of available keys for the datasetClassification map on the security specification
 * @type {Array}
 */
const datasetClassifiersKeys = Object.keys(datasetClassifiers);

// TODO: DSS-6671 Extract to constants module
const successUpdating = 'Your changes have been successfully saved!';
const failedUpdating = 'Oops! We are having trouble updating this dataset at the moment.';
const missingTypes = 'Looks like some fields are marked as `Confidential` or ' +
  '`Highly Confidential` but do not have a specified `Field Format`?';
const hiddenTrackingFieldsMsg = htmlSafe(
  '
Hey! Just a heads up that some fields in this dataset have been hidden from the table(s) below. ' +
  'These are tracking fields for which we\'ve been able to predetermine the compliance classification.
' +
  '
For example: header.memberId, requestHeader. ' +
  'Hopefully, this saves you some scrolling!
'
);

/**
 * Takes a string, returns a formatted string. Niche , single use case
 * for now, so no need to make into a helper
 * @param {String} string
 */
const formatAsCapitalizedStringWithSpaces = string =>
  string.replace(/[A-Z]/g, match => ` ${match}`).capitalize();

export default Component.extend({
  sortColumnWithName: 'identifierField',
  filterBy: 'identifierField',
  sortDirection: 'asc',
  searchTerm: '',
  hiddenTrackingFields: hiddenTrackingFieldsMsg,

  // Map classifiers to options better consumed by  drop down
  classifiers: classifiers.map(value => ({
    value,
    label: formatAsCapitalizedStringWithSpaces(value)
  })),

  // Map logicalTypes to options better consumed by  drop down
  logicalTypes: ['', ...logicalTypes.sort()].map(value => {
    const label = value ?
      value.replace(/_/g, ' ')
        .replace(/([A-Z]{3,})/g, f => f.toLowerCase().capitalize()) :
      'Not Specified';

    return {
      value,
      label
    };
  }),

  /**
   * Checks that all tags/ dataset content types have a boolean value
   * @type {Ember.computed}
   */
  isDatasetFullyClassified: computed('datasetClassification', function() {
    const datasetClassification = get(this, 'datasetClassification');

    return Object.keys(datasetClassification)
      .map(key => ({ value: datasetClassification[key].value }))
      .every(({ value }) => [true, false].includes(value));
  }),

  /**
   * Computed property that is dependent on all the keys in the datasetClassification map
   *   Returns a new map of datasetClassificationKey: String-> Object.<Boolean|undefined,String>
   * @type {Ember.computed}
   */
  datasetClassification: computed(`${datasetClassificationKey}.{${datasetClassifiersKeys.join(',')}}`, function() {
    const sourceDatasetClassification = get(this, datasetClassificationKey) || {};

    return Object.keys(datasetClassifiers).reduce((datasetClassification, classifier) => {
      datasetClassification[classifier] = {
        value: sourceDatasetClassification[classifier],
        label: datasetClassifiers[classifier]
      };

      return datasetClassification;
    }, {});
  }),

  /**
   * Creates a lookup table of fieldNames to classification
   *   Also, the expectation is that the association from fieldName -> classification
   *   is one-to-one hence no check to ensure a fieldName gets clobbered
   *   in the lookup assignment
   */
  fieldNameToClass: computed(
    `${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
    function () {
      const sourceClasses = getWithDefault(this, sourceClassificationKey, []);
      // Creates a lookup table of fieldNames to classification
      //   Also, the expectation is that the association from fieldName -> classification
      //   is one-to-one hence no check to ensure a fieldName gets clobbered
      //   in the lookup assignment
      return Object.keys(sourceClasses)
        .reduce((lookup, classificationKey) =>
            // For the provided classificationKey, iterate over it's fieldNames,
            //   and assign the classificationKey to the fieldName in the table
            (sourceClasses[classificationKey] || []).reduce((lookup, field) => {
              const { identifierField } = field;
              // cKey -> 1...fieldNameList => fieldName -> cKey
              lookup[identifierField] = classificationKey;
              return lookup;
            }, lookup),
          {}
        );
    }),

  /**
   * Lists all the dataset fields found in the `columns` api, and intersects
   *   each with the currently classified field names in
   *   securitySpecification.classification or null if not found
   */
  classificationDataFields: computed(
    `${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
    'schemaFieldNamesMappedToDataTypes',
    function () {
      // Set default or if already in policy, retrieve current values from
      //   privacyCompliancePolicy.compliancePurgeEntities
      return getWithDefault(
        this,
        'schemaFieldNamesMappedToDataTypes',
        []
      ).map(({ fieldName: identifierField, identifierType }) => {
        // Get the current classification list
        const currentClassLookup = get(this, 'fieldNameToClass');
        const classification = currentClassLookup[identifierField];

        // If the classification type exists, then find the identifierField, and
        //   assign to field, otherwise null
        //   Rather than assigning the default classification here, nulling gives the benefit of allowing
        //   subsequent consumer know that this field did not have a previous classification
        const field = classification ?
          get(this, `${sourceClassificationKey}.${classification}`)
            .findBy('identifierField', identifierField) :
          null;

        // Extract the logicalType from the field
        const logicalType = isPresent(field) ? field.logicalType : null;

        // Map to a new literal containing these props
        return {
          identifierType,
          identifierField,
          classification,
          logicalType
        };
      });
    }
  ),

  /**
   * @type {Boolean} cached boolean flag indicating that fields do contain a `kafka type`
   *    tracking header.
   *    Used to indicate to viewer that these fields are hidden.
   */
  containsHiddenTrackingFields: computed(
    'classificationDataFieldsSansHiddenTracking.length',
    function () {
      // If their is a diff in complianceDataFields and complianceDataFieldsSansHiddenTracking,
      //   then we have hidden tracking fields
      return get(this, 'classificationDataFieldsSansHiddenTracking.length') !== get(this, 'classificationDataFields.length');
    }),

  /**
   * @type {Array.<Object>} Filters the mapped confidential data fields without `kafka type`
   *   tracking headers
   */
  classificationDataFieldsSansHiddenTracking: computed('classificationDataFields.[]', function () {
    return get(this, 'classificationDataFields')
      .filter(({ identifierField }) => !isTrackingHeaderField(identifierField));
  }),

  /**
   * TODO: DSS-6672 Extract to notifications service
   * Helper method to update user when an async server update to the
   * security specification is handled.
   * @param {XMLHttpRequest|Promise|jqXHR|*} request the server request
   * @param {String} [successMessage] optional _message for successful response
   */
  whenRequestCompletes(request, { successMessage } = {}) {
    Promise.resolve(request)
      .then(({ status = 'error' }) => {
        // The server api currently responds with an object containing
        //   a status when complete
        return status === 'ok' ?
          setProperties(this, {
            _message: successMessage || successUpdating,
            _alertType: 'success'
          }) :
          Promise.reject(new Error(`Reason code for this is ${status}`));
      })
      .catch(err => {
        let _message = `${failedUpdating} \n ${err}`;
        let _alertType = 'danger';

        if (get(this, 'isNewSecuritySpecification')) {
          _message = 'This dataset does not have any ' +
            'previously saved fields with a Security Classification.';
          _alertType = 'info';
        }

        setProperties(this, {
          _message,
          _alertType
        });
      });
  },

  /**
   * TODO:DSS-6719 refactor into mixin
   * Clears recently shown user messages
   */
  clearMessages() {
    return setProperties(this, {
      _message: '',
      _alertType: ''
    });
  },

  /**
   * Takes an identifierField and a logicalType and updates the field on the
   * classification if it exists. Otherwise this is a no-op
   * @param {String} identifierField
   * @param {String} logicalType the type to be updated
   */
  changeFieldLogicalType(identifierField, logicalType) {
    const nextProps = { identifierField, logicalType };
    // The current classification name for the candidate identifier
    const currentClassLookup = get(this, 'fieldNameToClass');
    const defaultClassification = getWithDefault(this, `${sourceClassificationKey}.${defaultFieldDataTypeClassification[logicalType]}`, []);
    let currentClassificationName = currentClassLookup[identifierField];

    /**
     * If the field does not already have a classification, we set it's default
     * @link classificationDataFields
     */
    if (isBlank(currentClassificationName)) {
      currentClassificationName = defaultFieldDataTypeClassification[logicalType];
    }
    // The current classification list
    const currentClassification = get(this, `${sourceClassificationKey}.${currentClassificationName}`);

    if (!Array.isArray(currentClassification)) {
      throw new Error(`
      You have specified a classification object that is not a list ${currentClassification}.
      Ensure that the classification for this identifierField (${identifierField}) is
      set before attempting to change the logicalType.
      `);
    }

    const field = currentClassification.findBy('identifierField', identifierField);

    // Since this is change on the logical type, if the field already exists in a classification
    //   list, we remove it to allow applying the default for the new field type, below
    if (isPresent(field)) {
      // Remove identifierField from list
      currentClassification.setObjects(
        currentClassification.filter(
          ({ identifierField: fieldName }) => fieldName !== identifierField)
      );
    }

    // Update the default classification list
    return defaultClassification.setObjects([nextProps, ...defaultClassification]);
  },

  /**
   * Checks that each field in a given source classification has a value
   *   for the logicalType
   * @param {Array} sourceClassification
   */
  ensureFieldsContainLogicalType: (sourceClassification = []) =>
    sourceClassification.every(({ logicalType }) => logicalType),

  actions: {
    /**
     * Updates the logical type for a field with the provided identifierField
     * @param {String} identifierField the name of the field to update
     * @param {String} logicalType the updated logical type
     * @return {*|String|void}
     */
    updateLogicalType({ identifierField }, { value: logicalType }) {
      // TODO:DSS-6719 refactor into mixin
      this.clearMessages();
      return this.changeFieldLogicalType(identifierField, logicalType);
    },
    /**
     * Toggles the provided identifierField onto a classification list
     *   on securitySpecification.classification, identified by the provided
     *   classKey.
     * @param {Object} props field attributes for the field to update on the classification list
     * @param {String} classKey the name of the class to add, or potentially
     *   remove the identifierField from
     */
    updateClassification(props, { value: classKey }) {
      const { identifierField, logicalType, identifierType } = props;
      // fieldNames can be paths i.e. identifierField.identifierPath.subPath
      //   therefore, using Ember's `path lookup` syntax will not work
      const currentClassLookup = get(this, 'fieldNameToClass');
      const currentClass = currentClassLookup[identifierField];

      // TODO:DSS-6719 refactor into mixin
      this.clearMessages();
      // Since the association from identifierField -> classification is 1-to-1
      //  ensure that we do not currently have this identifierField
      // in any other classification lists by checking that the lookup is void
      if (!isBlank(currentClass)) {
        // Get the current classification list
        const currentClassification = get(
          this,
          `${sourceClassificationKey}.${currentClass}`
        );

        // Remove identifierField from list
        currentClassification.setObjects(
          currentClassification.filter(
            ({ identifierField: fieldName }) => fieldName !== identifierField)
        );
      }

      if (classKey) {
        // Get the candidate list
        let classification = get(
          this,
          `${sourceClassificationKey}.${classKey}`
        );
        // In the case that the list is not pre-populated,
        //  the value will be the default null, array ops won't work here
        //  ...so make array
        if (!classification) {
          classification = set(this, `${sourceClassificationKey}.${classKey}`, []);
        }

        // Finally perform operation
        classification.addObject(Object.assign({}, { identifierField, logicalType, identifierType }));
      }
    },

    /**
     * Updates the source object representing the current datasetClassification map
     * @param {String} classifier the property on the datasetClassification to update
     * @param {Boolean} value flag indicating if this dataset contains member data for the specified classifier
     */
    didChangeDatasetClassification(classifier, value) {
      let sourceDatasetClassification = getWithDefault(this, datasetClassificationKey, {});

      // For datasets initially without a datasetClassification, the default value is null
      if (sourceDatasetClassification === null) {
        sourceDatasetClassification = set(this, datasetClassificationKey, {});
      }

      return set(sourceDatasetClassification, classifier, value);
    },

    /**
     * Notify controller to propagate changes
     * @return {Boolean}
     */
    saveSecuritySpecification() {
      /**
       * For Each classifier ensure that the fields on the securitySpec
       *   contain a valid `logicalType` attribute
       * @type {Boolean}
       */
      const classedFieldsHaveLogicalType = classifiers.every(classifier =>
        this.ensureFieldsContainLogicalType(
          getWithDefault(this, `${sourceClassificationKey}.${classifier}`, [])
        ));

      if (classedFieldsHaveLogicalType) {
        this.whenRequestCompletes(get(this, 'onSave')());
      } else {
        setProperties(this, {
          _message: missingTypes,
          _alertType: 'danger'
        });
      }

      return false;
    },

    /**
     * Rolls back changes made to the compliance spec to current
     * server state
     */
    resetSecuritySpecification() {
      const options = {
        successMessage: 'Field classification has been reset to the previously saved state.'
      };
      this.whenRequestCompletes(get(this, 'onReset')(), options);
    }
  }
});
<|endoftext|>


<commit_before>module.exports = function(){
  var self = Ti.UI.createView({
    width: Ti.UI.FILL,
    height: Ti.UI.FILL,
    backgroundImage: 'images/bg-noise-pattern.png',
    backgroundRepeat: true
  });

  var line = Ti.UI.createView({
    height: Ti.UI.FILL,
    width: '5',
    backgroundColor: '#666',
    opacity: 0.1,
    left: '9%',
  });
  self.add(line);

  var scrollView = Ti.UI.createScrollView({
    layout : 'vertical',
    contentHeight : 'auto',
    scrollType : 'vertical',
    opacity: 1,
    zIndex: 99
  });
  self.add(scrollView);

  for (var i = 0; i < 20; i++) {
    var row = Ti.UI.createView({
      top: 15,
      height: 100,
      layout: 'horizontal',
      zIndex: 99
    });

    var sportIcon = Ti.UI.createImageView({
      image: 'images/soccer-icon.png',
      top: 25,
      left: '1%',
      height: 29,
      zIndex: 99
    });
    row.add(sportIcon);

    var gameInfoBox = Ti.UI.createView({
      backgroundImage: 'images/gameInfoBoxBg.png',
      height: '76',
      width: '75%',
      zIndex: 99
    });
    row.add(gameInfoBox);

    scrollView.add(row);
  }

  return self;
};
<commit_msg>Profile pic, name, and game joined now showing<commit_after>module.exports = function(){
  var self = Ti.UI.createView({
    width: Ti.UI.FILL,
    height: Ti.UI.FILL,
    backgroundImage: 'images/bg-noise-pattern.png',
    backgroundRepeat: true
  });

  var line = Ti.UI.createView({
    height: Ti.UI.FILL,
    width: '5',
    backgroundColor: '#666',
    opacity: 0.1,
    left: '9%'
  });
  self.add(line);

  var scrollView = Ti.UI.createScrollView({
    layout : 'vertical',
    contentHeight : 'auto',
    scrollType : 'vertical',
    opacity: 1,
    zIndex: 99
  });
  self.add(scrollView);

  for (var i = 0; i < 20; i++) {
    var row = Ti.UI.createView({
      top: 10,
      height: 100,
      layout: 'horizontal',
      zIndex: 99
    });

    var sportIcon = Ti.UI.createImageView({
      image: 'images/soccer-icon.png',
      top: 27,
      left: '1%',
      height: 29,
      zIndex: 99
    });
    row.add(sportIcon);

    var gameInfoBox = Ti.UI.createView({
      backgroundImage: 'images/gameInfoBoxBg.png',
      height: '84dp',
      left: -10,
      width: '80%',
    });
    row.add(gameInfoBox);

    var topHalf = Ti.UI.createView({
      layout: 'horizontal',
      bottom: '30%',
      left: -5,
      //backgroundColor: 'red'
    });
    gameInfoBox.add(topHalf);

    var profilePic = Ti.UI.createImageView({
      image: '/images/saad.png',
      top: 5,
      bottom: '16%',
    });
    topHalf.add(profilePic);

    var textContainer = Ti.UI.createView({
      layout: 'vertical',
      left: -15,
      top: 5,
      backgroundColor: 'green'
    });
    topHalf.add(textContainer);

    var name = Ti.UI.createLabel({
      text: 'Saad Malik',
      color: '#111',
      font: {fontStyle: 'bold', fontSize: '12dp'},
      textAlign: 'left',
      left: 0
    });

    var gameJoinedLine = Ti.UI.createView({
      layout: 'horizontal',
      top: -2,
      left: 0
    });

    var gameJoinedPrefix = Ti.UI.createLabel({
      text: 'joined a game of ',
      color: '#555',
      font: {fontStyle: 'bold', fontSize: '12dp'},
      textAlign: 'left',
      left: 0,
      top: 0
    });
    gameJoinedLine.add(gameJoinedPrefix);

    var gameJoined = Ti.UI.createLabel({
      text: 'Soccer',
      color: '#888',
      font: {fontStyle: 'bold', fontSize: '12dp'},
      textAlign: 'left',
      left: 0,
      top: 0
    });
    gameJoinedLine.add(gameJoined);

    var newLine = Ti.UI.createView({
      width: Ti.UI.FILL,
      backgroundColor: 'blue'
    });

    textContainer.add(name);
    textContainer.add(gameJoinedLine);
    textContainer.add(newLine);
/*
 *    var statsLine = Ti.UI.createView({
 *      layout: 'horizontal',
 *      left: 0,
 *      bottom: 0
 *    });
 *    textContainer.add(statsLine);
 *
 *    var locationPinImage = Ti.UI.createImageView({
 *      image: '/images/geoPin.png'
 *    });
 *    statsLine.add(locationPinImage);
 *    var distance = Ti.UI.createLabel({
 *      text: '0.3 mi',
 *      font: {fontSize: '12dp'},
 *      left: 0,
 *      color: '#999'
 *    });
 *    statsLine.add(distance);
 *
 *    var userCountImage = Ti.UI.createImageView({
 *      image: '/images/user.png',
 *      left: 0
 *    });
 *    statsLine.add(userCountImage);
 *
 *    var userCount = Ti.UI.createLabel({
 *      text: '8/12',
 *      font: {fontSize: '12dp'},
 *      left: 0,
 *      color: '#999'
 *    });
 *    statsLine.add(userCount);
 */

    scrollView.add(row);
  }

  return self;
};
<|endoftext|>


<commit_before>"use strict";

module.exports = function(config) {
    var fluid = require('infusion');

    var schemaHelper = require("../../schema/lib/schema-helper")(config);
    var namespace = "gpii.ctr.record";

    var record = fluid.registerNamespace(namespace);
    record.schema="record";

    var children = require('../lib/children')(config, record);

    var request = require('request');

    record.parseAndValidateInput = function() {
        if (!record.req.params || !record.req.params.uniqueId) {
            throw record.constructError(400,"You must provide the required query parameters to use this interface.");
        }

        if (record.req.query.children) {
            record.params.children = JSON.parse(record.req.query.children);
        }
    };

    // TODO:  create a library where we reuse patterns like this
    record.constructError = function(status, message) {
        var error = new Error(message);
        error.status = status;
        error.ok = false;
        return error;
    };

    var express = require('express');

    var router = express.Router();
    router.get('/:uniqueId', function(req, res){
        // per-request variables need to be defined here, otherwise (for example) the results of the previous search will be returned if the next search has no records
        record.req = req;
        record.res = res;
        record.params = {};

        // TODO:  Add support for "versions" virtual element comprised of attachments

        schemaHelper.setHeaders(res, "message");

        record.results = {
            "ok": true,
            "params": record.params,
            "retrievedAt": new Date()
        };

        // Server config validation
        if (!config || !config['couch.url']) {
            var message = "Your instance is not configured correctly to enable record lookup.  You must have a couch.url variable configured.";
            console.log(message);
            return res.send(500,JSON.stringify({ ok:false, message: message }));
        }

        // User input validation
        try {
            record.parseAndValidateInput();
        }
        catch(err) {
            return res.send(err.status ? err.status : 500, JSON.stringify(err));
        }

        // Get the record from couch
        request.get(config['couch.url'] + "/_design/api/_view/entries?keys=%5B%22" + record.req.params.uniqueId + "%22%5D", function(e,r,b) {
            if (e) { return res.send(500,JSON.stringify({ok: false, message: "Error retrieving record from couchdb", error: e}));}

            var jsonData = JSON.parse(b);
            if (!jsonData.rows || jsonData.rows.length === 0) {
                return res.send(404,JSON.stringify({ok:false, message: "Record not found." }));
            }
            else {
                record.results.record = jsonData.rows[0].value;

                if (record.results.record.type.toLowerCase() === "general" && record.params.children) {
                    // TODO:  This is a temporary hack to pass the predigested parent record data to the common
                    children.termHash = {};
                    children.termHash[record.results.record.uniqueId] = record.results.record;

                    // retrieve the child records via /tr/_design/api/_view/children?keys=
                    var childRecordOptions = {
                        "url" : config['couch.url'] + "/_design/api/_view/children?keys=" + JSON.stringify([record.results.record.uniqueId]),
                        "json": true
                    };

                    var request = require('request');
                    request.get(childRecordOptions, record.getChildRecords);
                }
                else {
                    schemaHelper.setHeaders(res, record.results.record.type);
                    res.send(200, JSON.stringify(record.results));
                }
            }
        });
    });

    router.post('/', function(req, res){
        // TODO:  Add Create REST end point (POST /api/record)

        // Create a new record based on the data submitted, adding the initial version field, set to 1
    });

    router.put('/', function(req, res){
        // TODO:  Add Update REST end point (PUT /api/record)

        // Add the new record as an unpublished draft attachment
    });

    router.get('/:uniqueId/publish', function(req, res){
        // TODO:  Add a mechanism for publishing drafts (GET /api/record/{id}/publish?version=X)

        // Get the current document's _rev value

        // Copy the existing record as an attachment of itself

        // Get the updated _rev value from the response

        // Retrieve the draft

        // Replace the document with the draft
    });

    router.delete('/:uniqueId', function(req, res){
        // TODO:  Add Delete REST end point (DELETE /api/record/{id})

        // Get the current document's _rev value

        // Copy the existing record as an attachment of itself

        // Get the updated _rev value from the response

        // Update the existing record to change its status to deleted

    });

    router.get("/", function(req,res) {
        schemaHelper.setHeaders(res, "message");
        return res.send(400,{ok: false, message: "You must provide the required uniqueId in the path to use this interface."});
    });

    return router;
};
<commit_msg>Saved work in progress on new PUT /api/record<commit_after>"use strict";

module.exports = function(config) {
    var fluid = require('infusion');

    var schemaHelper = require("../../schema/lib/schema-helper")(config);
    var namespace = "gpii.ctr.record";

    var record = fluid.registerNamespace(namespace);
    record.schema="record";

    var children = require('../lib/children')(config, record);

    var request = require('request');

    record.parseAndValidateInput = function() {
        if (!record.req.params || !record.req.params.uniqueId) {
            throw record.constructError(400,"You must provide the required query parameters to use this interface.");
        }

        if (record.req.query.children) {
            record.params.children = JSON.parse(record.req.query.children);
        }
    };

    // TODO:  create a library where we reuse patterns like this
    record.constructError = function(status, message) {
        var error = new Error(message);
        error.status = status;
        error.ok = false;
        return error;
    };

    var express = require('express');

    var router = express.Router();
    router.get('/:uniqueId', function(req, res){
        // per-request variables need to be defined here, otherwise (for example) the results of the previous search will be returned if the next search has no records
        record.req = req;
        record.res = res;
        record.params = {};

        // TODO:  Add support for "versions" virtual element comprised of attachments

        schemaHelper.setHeaders(res, "message");

        record.results = {
            "ok": true,
            "params": record.params,
            "retrievedAt": new Date()
        };

        // Server config validation
        if (!config || !config['couch.url']) {
            var message = "Your instance is not configured correctly to enable record lookup.  You must have a couch.url variable configured.";
            console.log(message);
            return res.send(500,JSON.stringify({ ok:false, message: message }));
        }

        // User input validation
        try {
            record.parseAndValidateInput();
        }
        catch(err) {
            return res.send(err.status ? err.status : 500, JSON.stringify(err));
        }

        // Get the record from couch
        request.get(config['couch.url'] + "/_design/api/_view/entries?keys=%5B%22" + record.req.params.uniqueId + "%22%5D", function(e,r,b) {
            if (e) { return res.send(500,JSON.stringify({ok: false, message: "Error retrieving record from couchdb", error: e}));}

            var jsonData = JSON.parse(b);
            if (!jsonData.rows || jsonData.rows.length === 0) {
                return res.send(404,JSON.stringify({ok:false, message: "Record not found." }));
            }
            else {
                record.results.record = jsonData.rows[0].value;

                if (record.results.record.type.toLowerCase() === "general" && record.params.children) {
                    // TODO:  This is a temporary hack to pass the predigested parent record data to the common
                    children.termHash = {};
                    children.termHash[record.results.record.uniqueId] = record.results.record;

                    // retrieve the child records via /tr/_design/api/_view/children?keys=
                    var childRecordOptions = {
                        "url" : config['couch.url'] + "/_design/api/_view/children?keys=" + JSON.stringify([record.results.record.uniqueId]),
                        "json": true
                    };

                    var request = require('request');
                    request.get(childRecordOptions, record.getChildRecords);
                }
                else {
                    schemaHelper.setHeaders(res, record.results.record.type);
                    res.send(200, JSON.stringify(record.results));
                }
            }
        });
    });

    router.post('/', function(req, res){
        // TODO:  Add Create REST end point (POST /api/record)

        // TODO:  Add support for versioning
    });

    // Update REST end point (PUT /api/record)
    router.put('/', function(req, res){
        // Make sure the current record has at least a uniqueId
        if (!req.params || !req.params.uniqueId) {
            return res.send(400,{"ok":"false","message": "You must provide a uniqueId of the record you wish to update."});
        }

        // TODO:  Make sure the user is logged in

        // Get the current document
        var readRequest = require('request');
        readRequest.get(config['couch.url'] + "/_design/api/_view/entries?key=%22" + req.params.uniqueId + "%22", function(readError,readResponse,readBody) {
            if (readError) {
                console.log(readError);
                return res.send(500,{"ok":"false","message": "There was an error retrieving the record with uniqueId '" + req.params.uniqueId + "'..."});
            }

            var jsonData = JSON.parse(readBody);
            var newRecord = {};
            if (!jsonData.rows || jsonData.rows.length === 0) {
                // Upload the supplied data as a new record
                newRecord = req.params;
            }
            else {
                var originalRecord = jsonData.rows[0].value;
                newRecord = JSON.parse(originalRecord.stringify());

                // TODO: Only allow data that matches the Schema
                var allowedFields = ["type","permanency","namespace","uniqueId","notes","status", "termLabel","valueSpace","source","aliasOf","translationOf", "definition", "uses", "applicationUniqueFlag"];

                // Overlay the supplied data onto this record, deleting any
                allowedFields.forEach(function(field){
                    if (req.params[field]) {
                        if (req.params[field] ===  null) {
                            newRecord[field].delete();
                        }
                        else {
                            newRecord[field] = req.params[field];
                        }
                    }
                });
            }

            // Set the "updated" field to the current date
            newRecord.updated = new Date().toISOString();

            // TODO: Set the "author" field to the current user

            // TODO: Validate the record against the relevant JSON schema

            // TODO: Make schema validation work for more than just the generic "record" type

            // TODO:  Add support for versioning

            // Upload the combined record to CouchDB
            var writeRequest = require('request');
            var writeOptions = {
                url: config['couch.url'] + "/",
                body: newRecord
            };

            writeRequest.put(writeOptions, function(writeError, writeResponse, writeBody){
                if (writeError) {
                    console.log(writeError);
                    return res.send(500,{"ok":"false","message": "There was an error writing the record with uniqueId '" + req.params.uniqueId + "'..."});
                }

                // TODO:  Return the updated record or update the API docs to indicate that we're not doing this.
                res.send(200,writeResponse);
            });
       });
    });

    router.get('/:uniqueId/publish', function(req, res){
        // TODO:  Add a mechanism for publishing drafts (GET /api/record/{id}/publish?version=X)

        // Get the current document's _rev value

        // Copy the existing record as an attachment of itself

        // Get the updated _rev value from the response

        // Retrieve the draft

        // Replace the document with the draft
    });

    router.delete('/:uniqueId', function(req, res){
        // TODO:  Add Delete REST end point (DELETE /api/record/{id})

        // Get the current document's _rev value

        // Copy the existing record as an attachment of itself

        // Get the updated _rev value from the response

        // Update the existing record to change its status to deleted

    });

    router.get("/", function(req,res) {
        schemaHelper.setHeaders(res, "message");
        return res.send(400,{ok: false, message: "You must provide the required uniqueId in the path to use this interface."});
    });

    return router;
};
<|endoftext|>


<commit_before>var gulp   = require("gulp");
var CFG    = require("./utils/config.js");
var $      = require("gulp-load-plugins")();
var exec   = require("child_process").exec;
var path   = require("path");
var notify = require("./utils/notify-style-lint");

/**
 * style:lint
 * @see github.com/causes/scss-lint
 * @see rubygems.org/gems/scss-lint
 */
gulp.task("style:lint", function (callback) {
  var scsslintProcess = exec("bundle " + [
    "exec",
    "scss-lint",
    "--config",
    path.join(CFG.FILE.config.styleLint),
    path.join(CFG.DIR.src, CFG.DIR.style)
  ].join(" "), function(err, stdout, stderr) {
    $.util.log("[style:lint] stdout:", stdout);
    $.util.log("[style:lint] stderr: ", stderr);

    notify(stdout);

    if(null !== err) {
      $.util.log("[style:lint] err: ", err);
    }

    callback();
  });
});
<commit_msg>Simplify arguments passed to exec.<commit_after>var gulp   = require("gulp");
var CFG    = require("./utils/config.js");
var $      = require("gulp-load-plugins")();
var exec   = require("child_process").exec;
var path   = require("path");
var notify = require("./utils/notify-style-lint");

/**
 * style:lint
 * @see github.com/causes/scss-lint
 * @see rubygems.org/gems/scss-lint
 */
gulp.task("style:lint", function (callback) {
  var scsslintProcess = exec([
    "bundle",
    "exec",
    "scss-lint",
    "--config",
    path.join(CFG.FILE.config.styleLint),
    path.join(CFG.DIR.src, CFG.DIR.style)
  ].join(" "), function(err, stdout, stderr) {
    $.util.log("[style:lint] stdout:", stdout);
    $.util.log("[style:lint] stderr: ", stderr);

    notify(stdout);

    if(null !== err) {
      $.util.log("[style:lint] err: ", err);
    }

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


<commit_before>export { ShuffleInfo };
export { ShuffleInfoPool };

import * as Pool from "../../util/Pool.js";

/**
 * The information for channel shuffler.
 *
 *
 * @member {number[]} concatenatedShape
 *   An array of integer describes the shape of the reshapeTransposeReshape()'s concatenated input tensor (tensor3d
 * or tensor1d). For example, if reshapeTransposeReshape() will be called with an array of image (i.e. array of
 * tensor3d), concatenatedShape should be [ height, width, totalChannelCount ]. Another example, if the input will
 * be an array of tensor1d, concatenatedShape should be [ totalChannelCount ]. No matter which example, the
 * totalChannelCount should always be the total sum of the last dimension size of all tensors in the
 * concatReshapeTransposeReshape()'s input array.
 *
 * @member {number} outputGroupCount
 *   If greater than 1, the input tensor3d list (after concatenated) will be shuffled and then splitted into so
 * many group. The ( totalChannelCount / outputGroupCount ) should be an integer. If less or equal than 1 (or null),
 * the output tensor3d list will just be an array with only one tensor3d which is the concatenation of all the
 * input tensor3d list (i.e. no shuffle and split).
 *
 * @member {ShuffleInfo} shuffleInfo
 *   The information calculated from concatenatedShape and outputGroupCount. (In fact, it is the this.)
 *
 *
 * @member {number} lastAxisId
 *   The last axis id of reshapeTransposeReshape()'s input tensor. It will be ( concatenatedShape.length - 1 ).
 *
 * @member {number} totalChannelCount
 *   The total channel count when all the concatReshapeTransposeReshape()'s input tensor concatenated. It will be
 * the value of the last element of concatenatedShape (i.e. concatenatedShape[ lastAxisId ]).
 *
 * @member {number} channelCountPerGroup
 *   There will be so many channels in one (output) group.
 *
 * @member {number[]} intermediateShape
 *   Before shuffling, the reshapeTransposeReshape()'s (concatenated) input will be reshaped to this intermediateShape.
 *
 * @member {number[]} transposePermutation
 *   After reshaped to intermediateShape, the (concatenated) input will be transposed according to this
 * transposePermutation (so that they are shuffled).
 *
 *
 * @member {number} tensorWeightCountExtracted
 *   The wieght count extracted from inputFloat32Array and used in tensors. Not including Params, because they are not used in
 * tensors. Not including inferenced weights (even if they are used in tensors), because they are not extracted from inputFloat32Array.
 *
 * @member {number} tensorWeightCountTotal
 *   The total wieght count used in tensors. Not including Params, because they are not used in tensors. Including inferenced
 * weights, if they are used in tensors.
 *
 *
 * @member {function} reshapeTransposeReshape
 *   Permute the input tensor by reshape-transpose-reshape. It is a function pointer to one of this.reshapeTransposeReshape_XXX().
 *
 * @member {function} reshapeTransposeReshapeSplit
 *   Permute and split the input tensor by reshape-transpose-reshape-split. It is a function pointer to one of
 * this.reshapeTransposeReshapeSplit_XXX().
 *
 * @member {function} concatReshapeTransposeReshape
 *   Concatenate and permute the input tensor by concat-reshape-transpose-reshape. It is a function pointer to one of
 * this.concatReshapeTransposeReshape_XXX().
 *
 * @member {function} concatReshapeTransposeReshapeSplit
 *   Concatenate, permute and split the input tensor by concat-reshape-transpose-reshape-split. It is a function pointer to one of
 * this.concatReshapeTransposeReshapeSplit_XXX().
 */
class ShuffleInfo {

  /**
   *
   */
  constructor( concatenatedShape, outputGroupCount ) {
    this.setAsConstructor( concatenatedShape, outputGroupCount );
  }

  /**
   *
   * @return {ShuffleInfo}
   *   Return the this object.
   */
  setAsConstructor( concatenatedShape, outputGroupCount ) {

    outputGroupCount = Math.trunc( outputGroupCount || 1 );
    if ( outputGroupCount < 1 )
      outputGroupCount = 1; // At least one (means: no shuffle and split (i.e. just concatenate only)).

    // Clone it (by shallow-copy) because the outside may modify it.
    {
      this.concatenatedShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length );
      for ( let i = 0; i < concatenatedShape.length; ++i ) {
        this.concatenatedShape[ i ] = concatenatedShape[ i ];
      }
    }

    this.outputGroupCount = outputGroupCount;

    this.shuffleInfo = this; // So that all ChannelShuffler.Xxx have property "shuffleInfo".

    let lastAxisId = this.lastAxisId = concatenatedShape.length - 1;
    let totalChannelCount = this.totalChannelCount = concatenatedShape[ lastAxisId ];

    // The channel count of every output group. (It should be an integer.)
    let channelCountPerGroup = this.channelCountPerGroup = totalChannelCount / outputGroupCount;

    // The shape before transpose. For example, if concatenatedShape is [ h, w, c ], the intermediateShape will be
    // [ h, w, outputGroupCount, channelCountPerGroup ]. The last dimension is splitted into two dimensions.
    //
    let intermediateShape;
    {

//!!! (2022/06/24 Remarke) Old Codes
//     let intermediateShape = this.intermediateShape = concatenatedShape.slice( 0, lastAxisId );
//     intermediateShape.push( outputGroupCount, channelCountPerGroup );

      intermediateShape = this.intermediateShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length + 1 );
      for ( let i = 0; i < lastAxisId; ++i ) {
        intermediateShape[ i ] = concatenatedShape[ i ];
      }
      intermediateShape[ lastAxisId     ] = outputGroupCount;
      intermediateShape[ lastAxisId + 1 ] = channelCountPerGroup;
    }

    // The axis permutation of transpose.
    //
    // For example, if the intermediateShape is [ h, w, outputGroupCount, channelCountPerGroup ]. Its
    // axis permutation will be [ 0, 1, 3, 2 ] so that the last two dimensions will be swapped.
    //
    let transposePermutation;
    {

//!!! (2022/06/24 Remarke) Old Codes
//    let transposePermutation = this.transposePermutation = new Array( ...intermediateShape.keys() );
//       let last1 = transposePermutation.pop();
//       let last2 = transposePermutation.pop();
//       transposePermutation.push( last1, last2 );

      transposePermutation = this.transposePermutation = Pool.Array.Singleton.get_or_create_by( intermediateShape.length );
      for ( let i = 0; i < transposePermutation.length; ++i ) {
        transposePermutation[ i ] = i;
      }

      let last1 = transposePermutation[ transposePermutation.length - 1 ];
      let last2 = transposePermutation[ transposePermutation.length - 2 ];
      transposePermutation[ transposePermutation.length - 2 ] = last1;
      transposePermutation[ transposePermutation.length - 1 ] = last2;
    }

    this.tensorWeightCountExtracted = 0;
    this.tensorWeightCountTotal = 0;

    this.reshapeTransposeReshape = this.reshapeTransposeReshape_dispose_finally_calls;
    this.reshapeTransposeReshapeSplit = this.reshapeTransposeReshapeSplit_dispose_finally_calls;
    this.concatReshapeTransposeReshape = this.concatReshapeTransposeReshape_dispose_finally_calls;
    this.concatReshapeTransposeReshapeSplit = this.concatReshapeTransposeReshapeSplit_dispose_finally_calls;

    return this;
  }

  /**
   * Release tf.tensor. (In fact, no tensors needed to be disposed in this ShuffleInfo.)
   *
   * Sub-class should override this method (and call super.disposeResources() before return).
   */
  disposeResources() {
    this.concatReshapeTransposeReshapeSplit = null;
    this.concatReshapeTransposeReshape = null;
    this.reshapeTransposeReshapeSplit = null;
    this.reshapeTransposeReshape = null;

    this.tensorWeightCountTotal = 0;
    this.tensorWeightCountExtracted = 0;

    Pool.Array.Singleton.recycle( this.transposePermutation );
    this.transposePermutation = null;

    Pool.Array.Singleton.recycle( this.intermediateShape );
    this.intermediateShape = null;

    this.channelCountPerGroup = 0;
    this.totalChannelCount = 0;
    this.lastAxisId = -1;
    this.shuffleInfo = null;
    this.outputGroupCount = 0;

    Pool.Array.Singleton.recycle( this.concatenatedShape );
    this.concatenatedShape = null;

    //super.disposeResources();
  }

  /**
   * After calling this method, this object should be viewed as disposed and should not be operated again.
   */
  disposeResources_and_recycleToPool() {
    this.disposeResources();
    ShuffleInfoPool.Singleton.recycle( this );
  }

  /** Not dispose the input. */
  reshape_to_intermediateShape_keep_input( t ) {
    return t.reshape( this.intermediateShape );
  }

  reshape_to_intermediateShape_dispose_input( t ) {
    try {
      return t.reshape( this.intermediateShape );
    } finally {
      t.dispose();
    }
  }

  transpose_accordingTo_transposePermutation_dispose_input( t ) {
    try {
      return t.transpose( this.transposePermutation );
    } finally {
      t.dispose();
    }
  }

  reshape_to_concatenatedShape_dispose_input( t ) {
    try {
      return t.reshape( this.concatenatedShape );
    } finally {
      t.dispose();
    }
  }

  split_to_outputGroupCount_along_lastAxisId_dispose_input( t ) {
    try {
      return t.split( this.outputGroupCount, this.lastAxisId );
    } finally {
      t.dispose();
    }
  }

  /**
   * Permute the input tensor by reshape-transpose-reshape.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its size is the same as concatenatedTensor but its last dimension is shuffled.
   */
  reshapeTransposeReshape_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Permute and split the input tensor by reshape-transpose-reshape-split.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenatedTensor, but their
   * last dimensions are shuffled.
   */
  reshapeTransposeReshapeSplit_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshape_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshapeSplit_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;

    // Because every single function try-finally to release tensor, the memory footprint could be reduced.
    // If using nested try-finally in one function to release tensors, the memory footprint will be larger.
  }

}

/**
 * Providing ChannelShuffler.ShuffleInfo
 *
 */
class ShuffleInfoPool extends Pool.Root {

  constructor() {
    super( "ChannelShuffler.ShuffleInfoPool", ShuffleInfo, ShuffleInfo.setAsConstructor );
  }

}

/**
 * Used as default ChannelShuffler.ShuffleInfo provider.
 */
ShuffleInfoPool.Singleton = new ShuffleInfoPool();

<commit_msg>Update ChannelShuffler_ShuffleInfo.js<commit_after>export { ShuffleInfo };
export { ShuffleInfoPool };

import * as Pool from "../../util/Pool.js";

/**
 * The information for channel shuffler.
 *
 *
 * @member {number[]} concatenatedShape
 *   An array of integer describes the shape of the reshapeTransposeReshape()'s concatenated input tensor (tensor3d
 * or tensor1d). For example, if reshapeTransposeReshape() will be called with an array of image (i.e. array of
 * tensor3d), concatenatedShape should be [ height, width, totalChannelCount ]. Another example, if the input will
 * be an array of tensor1d, concatenatedShape should be [ totalChannelCount ]. No matter which example, the
 * totalChannelCount should always be the total sum of the last dimension size of all tensors in the
 * concatReshapeTransposeReshape()'s input array.
 *
 * @member {number} outputGroupCount
 *   If greater than 1, the input tensor3d list (after concatenated) will be shuffled and then splitted into so
 * many group. The ( totalChannelCount / outputGroupCount ) should be an integer. If less or equal than 1 (or null),
 * the output tensor3d list will just be an array with only one tensor3d which is the concatenation of all the
 * input tensor3d list (i.e. no shuffle and split).
 *
 * @member {ShuffleInfo} shuffleInfo
 *   The information calculated from concatenatedShape and outputGroupCount. (In fact, it is the this.)
 *
 *
 * @member {number} lastAxisId
 *   The last axis id of reshapeTransposeReshape()'s input tensor. It will be ( concatenatedShape.length - 1 ).
 *
 * @member {number} totalChannelCount
 *   The total channel count when all the concatReshapeTransposeReshape()'s input tensor concatenated. It will be
 * the value of the last element of concatenatedShape (i.e. concatenatedShape[ lastAxisId ]).
 *
 * @member {number} channelCountPerGroup
 *   There will be so many channels in one (output) group.
 *
 * @member {number[]} intermediateShape
 *   Before shuffling, the reshapeTransposeReshape()'s (concatenated) input will be reshaped to this intermediateShape.
 *
 * @member {number[]} transposePermutation
 *   After reshaped to intermediateShape, the (concatenated) input will be transposed according to this
 * transposePermutation (so that they are shuffled).
 *
 *
 * @member {number} tensorWeightCountExtracted
 *   The wieght count extracted from inputFloat32Array and used in tensors. Not including Params, because they are not used in
 * tensors. Not including inferenced weights (even if they are used in tensors), because they are not extracted from inputFloat32Array.
 *
 * @member {number} tensorWeightCountTotal
 *   The total wieght count used in tensors. Not including Params, because they are not used in tensors. Including inferenced
 * weights, if they are used in tensors.
 *
 *
 * @member {function} reshapeTransposeReshape
 *   Permute the input tensor by reshape-transpose-reshape. It is a function pointer to one of this.reshapeTransposeReshape_XXX().
 *
 * @member {function} reshapeTransposeReshapeSplit
 *   Permute and split the input tensor by reshape-transpose-reshape-split. It is a function pointer to one of
 * this.reshapeTransposeReshapeSplit_XXX().
 *
 * @member {function} concatReshapeTransposeReshape
 *   Concatenate and permute the input tensor by concat-reshape-transpose-reshape. It is a function pointer to one of
 * this.concatReshapeTransposeReshape_XXX().
 *
 * @member {function} concatReshapeTransposeReshapeSplit
 *   Concatenate, permute and split the input tensor by concat-reshape-transpose-reshape-split. It is a function pointer to one of
 * this.concatReshapeTransposeReshapeSplit_XXX().
 */
class ShuffleInfo {

  /**
   *
   */
  constructor( concatenatedShape, outputGroupCount ) {
    ShuffleInfo.setAsConstructor.call( this, concatenatedShape, outputGroupCount );
  }

  /**
   *
   * @param {ShuffleInfo} this
   * The object to be initialized.
   *
   * @return {ShuffleInfo}
   *   Return the this object.
   */
  static setAsConstructor( concatenatedShape, outputGroupCount ) {

    outputGroupCount = Math.trunc( outputGroupCount || 1 );
    if ( outputGroupCount < 1 )
      outputGroupCount = 1; // At least one (means: no shuffle and split (i.e. just concatenate only)).

    // Clone it (by shallow-copy) because the outside may modify it.
    {
      this.concatenatedShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length );
      for ( let i = 0; i < concatenatedShape.length; ++i ) {
        this.concatenatedShape[ i ] = concatenatedShape[ i ];
      }
    }

    this.outputGroupCount = outputGroupCount;

    this.shuffleInfo = this; // So that all ChannelShuffler.Xxx have property "shuffleInfo".

    let lastAxisId = this.lastAxisId = concatenatedShape.length - 1;
    let totalChannelCount = this.totalChannelCount = concatenatedShape[ lastAxisId ];

    // The channel count of every output group. (It should be an integer.)
    let channelCountPerGroup = this.channelCountPerGroup = totalChannelCount / outputGroupCount;

    // The shape before transpose. For example, if concatenatedShape is [ h, w, c ], the intermediateShape will be
    // [ h, w, outputGroupCount, channelCountPerGroup ]. The last dimension is splitted into two dimensions.
    //
    let intermediateShape;
    {

//!!! (2022/06/24 Remarke) Old Codes
//     let intermediateShape = this.intermediateShape = concatenatedShape.slice( 0, lastAxisId );
//     intermediateShape.push( outputGroupCount, channelCountPerGroup );

      intermediateShape = this.intermediateShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length + 1 );
      for ( let i = 0; i < lastAxisId; ++i ) {
        intermediateShape[ i ] = concatenatedShape[ i ];
      }
      intermediateShape[ lastAxisId     ] = outputGroupCount;
      intermediateShape[ lastAxisId + 1 ] = channelCountPerGroup;
    }

    // The axis permutation of transpose.
    //
    // For example, if the intermediateShape is [ h, w, outputGroupCount, channelCountPerGroup ]. Its
    // axis permutation will be [ 0, 1, 3, 2 ] so that the last two dimensions will be swapped.
    //
    let transposePermutation;
    {

//!!! (2022/06/24 Remarke) Old Codes
//    let transposePermutation = this.transposePermutation = new Array( ...intermediateShape.keys() );
//       let last1 = transposePermutation.pop();
//       let last2 = transposePermutation.pop();
//       transposePermutation.push( last1, last2 );

      transposePermutation = this.transposePermutation = Pool.Array.Singleton.get_or_create_by( intermediateShape.length );
      for ( let i = 0; i < transposePermutation.length; ++i ) {
        transposePermutation[ i ] = i;
      }

      let last1 = transposePermutation[ transposePermutation.length - 1 ];
      let last2 = transposePermutation[ transposePermutation.length - 2 ];
      transposePermutation[ transposePermutation.length - 2 ] = last1;
      transposePermutation[ transposePermutation.length - 1 ] = last2;
    }

    this.tensorWeightCountExtracted = 0;
    this.tensorWeightCountTotal = 0;

    this.reshapeTransposeReshape = this.reshapeTransposeReshape_dispose_finally_calls;
    this.reshapeTransposeReshapeSplit = this.reshapeTransposeReshapeSplit_dispose_finally_calls;
    this.concatReshapeTransposeReshape = this.concatReshapeTransposeReshape_dispose_finally_calls;
    this.concatReshapeTransposeReshapeSplit = this.concatReshapeTransposeReshapeSplit_dispose_finally_calls;

    return this;
  }

  /**
   * Release tf.tensor. (In fact, no tensors needed to be disposed in this ShuffleInfo.)
   *
   * Sub-class should override this method (and call super.disposeResources() before return).
   */
  disposeResources() {
    this.concatReshapeTransposeReshapeSplit = null;
    this.concatReshapeTransposeReshape = null;
    this.reshapeTransposeReshapeSplit = null;
    this.reshapeTransposeReshape = null;

    this.tensorWeightCountTotal = 0;
    this.tensorWeightCountExtracted = 0;

    Pool.Array.Singleton.recycle( this.transposePermutation );
    this.transposePermutation = null;

    Pool.Array.Singleton.recycle( this.intermediateShape );
    this.intermediateShape = null;

    this.channelCountPerGroup = 0;
    this.totalChannelCount = 0;
    this.lastAxisId = -1;
    this.shuffleInfo = null;
    this.outputGroupCount = 0;

    Pool.Array.Singleton.recycle( this.concatenatedShape );
    this.concatenatedShape = null;

    //super.disposeResources();
  }

  /**
   * After calling this method, this object should be viewed as disposed and should not be operated again.
   */
  disposeResources_and_recycleToPool() {
    this.disposeResources();
    ShuffleInfoPool.Singleton.recycle( this );
  }

  /** Not dispose the input. */
  reshape_to_intermediateShape_keep_input( t ) {
    return t.reshape( this.intermediateShape );
  }

  reshape_to_intermediateShape_dispose_input( t ) {
    try {
      return t.reshape( this.intermediateShape );
    } finally {
      t.dispose();
    }
  }

  transpose_accordingTo_transposePermutation_dispose_input( t ) {
    try {
      return t.transpose( this.transposePermutation );
    } finally {
      t.dispose();
    }
  }

  reshape_to_concatenatedShape_dispose_input( t ) {
    try {
      return t.reshape( this.concatenatedShape );
    } finally {
      t.dispose();
    }
  }

  split_to_outputGroupCount_along_lastAxisId_dispose_input( t ) {
    try {
      return t.split( this.outputGroupCount, this.lastAxisId );
    } finally {
      t.dispose();
    }
  }

  /**
   * Permute the input tensor by reshape-transpose-reshape.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its size is the same as concatenatedTensor but its last dimension is shuffled.
   */
  reshapeTransposeReshape_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Permute and split the input tensor by reshape-transpose-reshape-split.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenatedTensor, but their
   * last dimensions are shuffled.
   */
  reshapeTransposeReshapeSplit_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshape_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshapeSplit_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;

    // Because every single function try-finally to release tensor, the memory footprint could be reduced.
    // If using nested try-finally in one function to release tensors, the memory footprint will be larger.
  }

}

/**
 * Providing ChannelShuffler.ShuffleInfo
 *
 */
class ShuffleInfoPool extends Pool.Root {

  constructor() {
    super( "ChannelShuffler.ShuffleInfoPool", ShuffleInfo, ShuffleInfo.setAsConstructor );
  }

}

/**
 * Used as default ChannelShuffler.ShuffleInfo provider.
 */
ShuffleInfoPool.Singleton = new ShuffleInfoPool();

<|endoftext|>


<commit_before>import macro from 'vtk.js/Sources/macro';
import vtkCompositeMouseManipulator from 'vtk.js/Sources/Interaction/Manipulators/CompositeMouseManipulator';

// ----------------------------------------------------------------------------
// vtkMouseRangeManipulator methods
// ----------------------------------------------------------------------------

function vtkMouseRangeManipulator(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkMouseRangeManipulator');

  // Keep track of delta that is below the value
  // of one step to progressively increment it
  model.incrementalDelta = {};

  // Internal methods
  //-------------------------------------------------------------------------
  function scaleDeltaToRange(listener, normalizedDelta) {
    return (
      normalizedDelta * ((listener.max - listener.min) / (listener.step + 1))
    );
  }

  //-------------------------------------------------------------------------
  function processDelta(listener, delta) {
    const oldValue = listener.getValue();
    let value = oldValue + delta + model.incrementalDelta[listener];

    const difference = value - listener.min;
    const stepsToDifference = difference / listener.step;
    value = listener.min + listener.step * stepsToDifference;
    value = Math.max(value, listener.min);
    value = Math.min(value, listener.max);

    // Check if value will change or if we need to store
    // the delta to append at the next iteration
    if (value !== oldValue) {
      listener.setValue(value);
      model.incrementalDelta[listener] = 0;
    } else {
      model.incrementalDelta[listener] += delta;
      // Do not allow incremental delta to go past range
      if (
        (value === listener.min && model.incrementalDelta[listener] < 0) ||
        (value === listener.max && model.incrementalDelta[listener] > 0)
      ) {
        model.incrementalDelta[listener] = 0;
      }
    }
  }

  // Public API methods
  //-------------------------------------------------------------------------
  publicAPI.setHorizontalListener = (min, max, step, getValue, setValue) => {
    const getFn = Number.isFinite(getValue) ? () => getValue : getValue;
    model.horizontalListener = { min, max, step, getValue: getFn, setValue };
    model.incrementalDelta[model.horizontalListener] = 0;
    publicAPI.modified();
  };

  //-------------------------------------------------------------------------
  publicAPI.setVerticalListener = (min, max, step, getValue, setValue) => {
    const getFn = Number.isFinite(getValue) ? () => getValue : getValue;
    model.verticalListener = { min, max, step, getValue: getFn, setValue };
    model.incrementalDelta[model.verticalListener] = 0;
    publicAPI.modified();
  };

  //-------------------------------------------------------------------------
  publicAPI.setScrollListener = (min, max, step, getValue, setValue) => {
    const getFn = Number.isFinite(getValue) ? () => getValue : getValue;
    model.scrollListener = { min, max, step, getValue: getFn, setValue };
    model.incrementalDelta[model.scrollListener] = 0;
    publicAPI.modified();
  };

  //-------------------------------------------------------------------------
  publicAPI.removeHorizontalListener = () => {
    if (model.verticalListener) {
      delete model.verticalListener;
      publicAPI.modified();
    }
  };

  //-------------------------------------------------------------------------
  publicAPI.removeVerticalListener = () => {
    if (model.horizontalListener) {
      delete model.horizontalListener;
      publicAPI.modified();
    }
  };

  //-------------------------------------------------------------------------
  publicAPI.removeScrollListener = () => {
    if (model.scrollListener) {
      delete model.scrollListener;
      publicAPI.modified();
    }
  };

  //-------------------------------------------------------------------------
  publicAPI.removeAllListeners = () => {
    publicAPI.removeHorizontalListener();
    publicAPI.removeVerticalListener();
    publicAPI.removeScrollListener();
  };

  //-------------------------------------------------------------------------
  publicAPI.onButtonDown = (interactor, renderer, position) => {
    model.previousPosition = position;
  };

  //-------------------------------------------------------------------------
  publicAPI.onMouseMove = (interactor, renderer, position) => {
    if (!model.verticalListener && !model.horizontalListener) {
      return;
    }
    if (!position) {
      return;
    }

    // Normalize by viewport size
    const size = interactor.getView().getViewportSize(renderer);

    if (model.horizontalListener) {
      const dxNorm = (position.x - model.previousPosition.x) / size[0];
      const dx = scaleDeltaToRange(model.horizontalListener, dxNorm);
      processDelta(model.horizontalListener, dx);
    }
    if (model.verticalListener) {
      const dyNorm = (position.y - model.previousPosition.y) / size[1];
      const dy = scaleDeltaToRange(model.verticalListener, dyNorm);
      processDelta(model.verticalListener, dy);
    }

    model.previousPosition = position;
  };

  //-------------------------------------------------------------------------
  publicAPI.onScroll = (interactor, renderer, delta) => {
    if (!model.scrollListener || !delta) {
      return;
    }
    processDelta(model.scrollListener, delta * model.scrollListener.step);
  };
  publicAPI.onStartScroll = publicAPI.onScroll;
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  horizontalListener: null,
  verticalListener: null,
  scrollListener: null,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Inheritance
  macro.obj(publicAPI, model);
  vtkCompositeMouseManipulator.extend(publicAPI, model, initialValues);

  // Object specific methods
  vtkMouseRangeManipulator(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(
  extend,
  'vtkMouseRangeManipulator'
);

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<commit_msg>fix(MouseRangeManipulator): Objects cannot have object keys<commit_after>import macro from 'vtk.js/Sources/macro';
import vtkCompositeMouseManipulator from 'vtk.js/Sources/Interaction/Manipulators/CompositeMouseManipulator';

// ----------------------------------------------------------------------------
// vtkMouseRangeManipulator methods
// ----------------------------------------------------------------------------

function vtkMouseRangeManipulator(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkMouseRangeManipulator');

  // Keep track of delta that is below the value
  // of one step to progressively increment it
  const incrementalDelta = new Map();

  // Internal methods
  //-------------------------------------------------------------------------
  function scaleDeltaToRange(listener, normalizedDelta) {
    return (
      normalizedDelta * ((listener.max - listener.min) / (listener.step + 1))
    );
  }

  //-------------------------------------------------------------------------
  function processDelta(listener, delta) {
    const oldValue = listener.getValue();
    let value = oldValue + delta + incrementalDelta.get(listener);

    const difference = value - listener.min;
    const stepsToDifference = difference / listener.step;
    value = listener.min + listener.step * stepsToDifference;
    value = Math.max(value, listener.min);
    value = Math.min(value, listener.max);

    // Check if value will change or if we need to store
    // the delta to append at the next iteration
    if (value !== oldValue) {
      listener.setValue(value);
      incrementalDelta.set(listener, 0);
    } else {
      const v = incrementalDelta.get(listener);
      incrementalDelta.set(listener, v + delta);
      // Do not allow incremental delta to go past range
      if (
        (value === listener.min && incrementalDelta.get(listener) < 0) ||
        (value === listener.max && incrementalDelta.get(listener) > 0)
      ) {
        incrementalDelta.set(listener, 0);
      }
    }
  }

  // Public API methods
  //-------------------------------------------------------------------------
  publicAPI.setHorizontalListener = (min, max, step, getValue, setValue) => {
    const getFn = Number.isFinite(getValue) ? () => getValue : getValue;
    model.horizontalListener = { min, max, step, getValue: getFn, setValue };
    incrementalDelta.set(model.horizontalListener, 0);
    publicAPI.modified();
  };

  //-------------------------------------------------------------------------
  publicAPI.setVerticalListener = (min, max, step, getValue, setValue) => {
    const getFn = Number.isFinite(getValue) ? () => getValue : getValue;
    model.verticalListener = { min, max, step, getValue: getFn, setValue };
    incrementalDelta.set(model.verticalListener, 0);
    publicAPI.modified();
  };

  //-------------------------------------------------------------------------
  publicAPI.setScrollListener = (min, max, step, getValue, setValue) => {
    const getFn = Number.isFinite(getValue) ? () => getValue : getValue;
    model.scrollListener = { min, max, step, getValue: getFn, setValue };
    incrementalDelta.set(model.scrollListener, 0);
    publicAPI.modified();
  };

  //-------------------------------------------------------------------------
  publicAPI.removeHorizontalListener = () => {
    if (model.verticalListener) {
      delete model.verticalListener;
      publicAPI.modified();
    }
  };

  //-------------------------------------------------------------------------
  publicAPI.removeVerticalListener = () => {
    if (model.horizontalListener) {
      delete model.horizontalListener;
      publicAPI.modified();
    }
  };

  //-------------------------------------------------------------------------
  publicAPI.removeScrollListener = () => {
    if (model.scrollListener) {
      delete model.scrollListener;
      publicAPI.modified();
    }
  };

  //-------------------------------------------------------------------------
  publicAPI.removeAllListeners = () => {
    publicAPI.removeHorizontalListener();
    publicAPI.removeVerticalListener();
    publicAPI.removeScrollListener();
  };

  //-------------------------------------------------------------------------
  publicAPI.onButtonDown = (interactor, renderer, position) => {
    model.previousPosition = position;
  };

  //-------------------------------------------------------------------------
  publicAPI.onMouseMove = (interactor, renderer, position) => {
    if (!model.verticalListener && !model.horizontalListener) {
      return;
    }
    if (!position) {
      return;
    }

    // Normalize by viewport size
    const size = interactor.getView().getViewportSize(renderer);

    if (model.horizontalListener) {
      const dxNorm = (position.x - model.previousPosition.x) / size[0];
      const dx = scaleDeltaToRange(model.horizontalListener, dxNorm);
      processDelta(model.horizontalListener, dx);
    }
    if (model.verticalListener) {
      const dyNorm = (position.y - model.previousPosition.y) / size[1];
      const dy = scaleDeltaToRange(model.verticalListener, dyNorm);
      processDelta(model.verticalListener, dy);
    }

    model.previousPosition = position;
  };

  //-------------------------------------------------------------------------
  publicAPI.onScroll = (interactor, renderer, delta) => {
    if (!model.scrollListener || !delta) {
      return;
    }
    processDelta(model.scrollListener, delta * model.scrollListener.step);
  };
  publicAPI.onStartScroll = publicAPI.onScroll;
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  horizontalListener: null,
  verticalListener: null,
  scrollListener: null,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Inheritance
  macro.obj(publicAPI, model);
  vtkCompositeMouseManipulator.extend(publicAPI, model, initialValues);

  // Object specific methods
  vtkMouseRangeManipulator(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(
  extend,
  'vtkMouseRangeManipulator'
);

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<|endoftext|>


<commit_before>"use strict";

function SkinnedWireframeHelper(object, hex)
{
	THREE.SkinnedMesh.call(this, object.geometry, new THREE.MeshBasicMaterial(
	{
		color: (hex !== undefined) ? hex : 0xFFFFFF,
		wireframe: true,
		skinning: true
	}));

	this.object = object;
	this.matrixAutoUpdate = false;

	this.update();
}

SkinnedWireframeHelper.prototype = Object.create(THREE.SkinnedMesh.prototype);

SkinnedWireframeHelper.prototype.update = function()
{
	this.matrixWorld.copy(this.object.matrixWorld);
};
<commit_msg>Derp<commit_after>"use strict";

function SkinnedWireframeHelper(object, hex)
{
	THREE.SkinnedMesh.call(this, object.geometry, new THREE.MeshBasicMaterial(
	{
		color: (hex !== undefined) ? hex : 0xFFFFFF,
		wireframe: true,
		skinning: true
	}));

	this.object = object;
	this.matrixAutoUpdate = false;
	this.update();
}

SkinnedWireframeHelper.prototype = Object.create(THREE.SkinnedMesh.prototype);

SkinnedWireframeHelper.prototype.update = function()
{
	this.matrixWorld.copy(this.object.matrixWorld);
};
<|endoftext|>


<commit_before>
angular.module('forms', ['duScroll', 'ngResource', 'NodeForm.templates', 'pascalprecht.translate']);

angular.module('NodeForm', [
    'ui.select', 'cgBusy', 'ngSanitize', 'vButton', 'ngResource',
    'ui.router', 'ui.bootstrap', 'ui.utils', 'ngRaven', 'forms'
]);

angular.module('forms').factory('Auth', [
  function() {
    var service = {
      _currentUser: null,
      get currentUser(){
        return this._currentUser;
      },
      ensureHasCurrentUser: function() {
        return null;
      },
      isAuthenticated: function() {
        return false;
      },
      getUserState: function() {
        return '';
      },
      login: function() {
      },
      logout: function() {
      },
    };
    return service;
  }
]);
angular.module('forms').factory('$state', [function() {
    return {
        go: function() {}
    };
}]);
angular.module('forms').factory('myForm', ['Forms', function(Forms) {
    var form = window.form;
    form.visible_form_fields = _.filter(form.form_fields, function(field){
    	return (field.deletePreserved === false);
    });
    return form;
}]);
angular.module('forms').constant('FORM_URL', '/form/:formId');

angular.element(document).ready(function() {
	//Then init the app
	angular.bootstrap(document, ['forms']);
});
<commit_msg>fix star in demo<commit_after>
angular.module('forms', [
	'duScroll', 'ngResource', 'NodeForm.templates', 'pascalprecht.translate',
	'angular-input-stars'
]);

angular.module('NodeForm', [
    'ui.select', 'cgBusy', 'ngSanitize', 'vButton', 'ngResource',
    'ui.router', 'ui.bootstrap', 'ui.utils', 'ngRaven', 'forms'
]);

angular.module('forms').factory('Auth', [
  function() {
    var service = {
      _currentUser: null,
      get currentUser(){
        return this._currentUser;
      },
      ensureHasCurrentUser: function() {
        return null;
      },
      isAuthenticated: function() {
        return false;
      },
      getUserState: function() {
        return '';
      },
      login: function() {
      },
      logout: function() {
      },
    };
    return service;
  }
]);
angular.module('forms').factory('$state', [function() {
    return {
        go: function() {}
    };
}]);
angular.module('forms').factory('myForm', ['Forms', function(Forms) {
    var form = window.form;
    form.visible_form_fields = _.filter(form.form_fields, function(field){
    	return (field.deletePreserved === false);
    });
    return form;
}]);
angular.module('forms').constant('FORM_URL', '/form/:formId');

angular.element(document).ready(function() {
	//Then init the app
	angular.bootstrap(document, ['forms']);
});
<|endoftext|>


<commit_before>exports = module.exports = function(container) {
  return container.create('./statestore/session');
};

exports['@implements'] = 'http://i.bixbyjs.org/http/ua/flows/StateStore';
exports['@singleton'] = true;
exports['@require'] = [ '!container' ];
<commit_msg>Fix implements annotation.<commit_after>exports = module.exports = function(container) {
  return container.create('./statestore/session');
};

exports['@implements'] = 'http://i.bixbyjs.org/www/ceremony/StateStore';
exports['@singleton'] = true;
exports['@require'] = [ '!container' ];
<|endoftext|>


<commit_before>/* Copyright (C) 2017 Canonical Ltd. */

'use strict';

class Profile extends React.Component {

  render() {
    return (
      <juju.components.Panel
        instanceName="profile"
        visible={true}>
        <juju.components.ProfileHeader />

            <juju.components.ProfileNavigation />

      </juju.components.Panel>
    );
  }

};

Profile.propTypes = {};

YUI.add('profile', function() {
  juju.components.Profile = Profile;
}, '', {
  requires: [
    'profile-navigation',
    'profile-header'
  ]
});
<commit_msg>Depending on the active section prop and state, render the appropriate placeholder content.<commit_after>/* Copyright (C) 2017 Canonical Ltd. */

'use strict';

class Profile extends React.Component {

  constructor(props) {
    super(props);
    const activeSection = decodeURIComponent(this.props.activeSection || 'models'); // eslint-disable-line
    this.state = {activeSection};
  }

  componentWillReceiveProps(nextProps) {
    this.setState({activeSection: nextProps.activeSection});
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.activeSection !== this.state.activeSection) {
      return true;
    }
    return false;
  }

  _generateContent() {
    switch(this.state.activeSection) {
      case 'models':
        return 'Models';
        break;
      case 'bundles':
        return 'Bundles';
        break;
      case 'charms':
        return 'Charms';
        break;
      case 'cloud credentials':
        return 'Cloud Credentials';
        break;
      default:
        // Render the default as the model list for now, but the Summary later.
        return 'Models';
        break;
    }
  }

  render() {
    return (
      <juju.components.Panel
        instanceName="profile"
        visible={true}>
        <juju.components.ProfileHeader />

            <juju.components.ProfileNavigation
              changeState={this.props.changeState}/>

            {this._generateContent()}

      </juju.components.Panel>
    );
  }

};

Profile.propTypes = {
  activeSection: React.PropTypes.string,
  changeState: React.PropTypes.func.isRequired
};

YUI.add('profile', function() {
  juju.components.Profile = Profile;
}, '', {
  requires: [
    'profile-navigation',
    'profile-header'
  ]
});
<|endoftext|>