<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

import _ from 'lodash';
import moment from 'moment';
import i18next from 'i18next';

import { getPumpVocabulary } from '../device';
import {
  generateBgRangeLabels,
  reshapeBgClassesToBgBounds,
} from '../bloodglucose';

import {
  SITE_CHANGE_RESERVOIR,
  SITE_CHANGE_TUBING,
  SITE_CHANGE_CANNULA,
  SITE_CHANGE_TYPE_UNDECLARED,
  AUTOMATED_DELIVERY,
  INSULET,
  TANDEM,
  ANIMAS,
  MEDTRONIC,
  pumpVocabulary,
} from '../constants';

import TextUtil from '../text/TextUtil';
import { statsText } from '../stat';

const t = i18next.t.bind(i18next);

// Exporting utils for easy stubbing in tests
export const utils = {
  reshapeBgClassesToBgBounds,
  statsText,
  TextUtil,
};

/**
 * Define sections and dimensions used in the basics view
 *
 * @param {Object} bgPrefs - bgPrefs object containing viz-style bgBounds
 * @returns {Object} sections
 */
export function defineBasicsAggregations(bgPrefs, manufacturer) {
  const bgLabels = generateBgRangeLabels(bgPrefs);
  bgLabels.veryLow = _.upperFirst(bgLabels.veryLow);
  bgLabels.veryHigh = _.upperFirst(bgLabels.veryHigh);

  const deviceLabels = getPumpVocabulary(manufacturer);

  const sectionNames = [
    'basals',
    'boluses',
    'fingersticks',
    'siteChanges',
  ];

  const sections = {};

  _.each(sectionNames, section => {
    let type = section;
    let dimensions;
    let title = '';
    let summaryTitle;
    let emptyText;
    const active = true;

    switch (section) {
      case 'basals':
        title = 'Basals';
        summaryTitle = t('Total basal events');
        dimensions = [
          { path: 'summary', key: 'total', label: t('Basal Events'), primary: true },
          { path: 'summary.subtotals', key: 'temp', label: t('Temp Basals') },
          { path: 'summary.subtotals', key: 'suspend', label: t('Suspends') },
          {
            path: 'summary.subtotals',
            key: 'automatedStop',
            label: t('{{automatedLabel}} Exited', {
              automatedLabel: deviceLabels[AUTOMATED_DELIVERY],
            }),
            hideEmpty: true,
          },
        ];
        break;

      case 'boluses':
        title = t('Bolusing');
        summaryTitle = t('Avg boluses / day');
        dimensions = [
          { path: 'summary', key: 'total', label: t('Avg per day'), average: true, primary: true },
          { path: 'summary.subtotals', key: 'wizard', label: t('Calculator'), percentage: true, selectorIndex: 0 },
          { path: 'summary.subtotals', key: 'correction', label: t('Correction'), percentage: true, selectorIndex: 1 },
          { path: 'summary.subtotals', key: 'extended', label: t('Extended'), percentage: true, selectorIndex: 3 },
          { path: 'summary.subtotals', key: 'interrupted', label: t('Interrupted'), percentage: true, selectorIndex: 4 },
          { path: 'summary.subtotals', key: 'override', label: t('Override'), percentage: true, selectorIndex: 2 },
          { path: 'summary.subtotals', key: 'underride', label: t('Underride'), percentage: true, selectorIndex: 5 },
        ];
        break;

      case 'fingersticks':
        title = t('BG readings');
        summaryTitle = t('Avg BG readings / day');
        dimensions = [
          { path: 'smbg.summary', key: 'total', label: t('Avg per day'), average: true, primary: true },
          { path: 'smbg.summary.subtotals', key: 'meter', label: t('Meter'), percentage: true },
          { path: 'smbg.summary.subtotals', key: 'manual', label: t('Manual'), percentage: true },
          { path: 'calibration.summary.subtotals', key: 'calibration', label: t('Calibrations'), hideEmpty: true },
          { path: 'smbg.summary.subtotals', key: 'veryLow', label: bgLabels.veryLow, percentage: true },
          { path: 'smbg.summary.subtotals', key: 'veryHigh', label: bgLabels.veryHigh, percentage: true },
        ];
        break;

      case 'siteChanges':
        title = t('Infusion site changes');
        break;

      default:
        type = false;
        break;
    }

    sections[section] = {
      active,
      title,
      summaryTitle,
      emptyText,
      type,
      dimensions,
    };
  });

  return sections;
}

/**
 * Generate the day labels based on the days supplied by the processed basics view data
 *
 * @export
 * @param {Array} days - supplied by the processed basics view data
 * @returns {Array} labels - formatted day labels.  I.E. [Mon, Tues, Wed, ...]
 */
export function generateCalendarDayLabels(days) {
  const firstDay = moment.utc(days[0].date).day();

  return _.map(_.range(firstDay, firstDay + 7), dow => (
    moment.utc().day(dow).format('ddd')
  ));
}

/**
 * Determine the site change source for the patient
 * @param {Object} patient
 * @param {String} manufacturer
 */
export function getSiteChangeSource(patient = {}, manufacturer) {
  const {
    settings,
  } = patient;

  let siteChangeSource = SITE_CHANGE_TYPE_UNDECLARED;

  if (_.includes(_.map([ANIMAS, MEDTRONIC, TANDEM], _.lowerCase), manufacturer)) {
    siteChangeSource = _.get(settings, 'siteChangeSource');
    const allowedSources = [SITE_CHANGE_CANNULA, SITE_CHANGE_TUBING];

    if (!_.includes(allowedSources, siteChangeSource)) {
      siteChangeSource = SITE_CHANGE_TYPE_UNDECLARED;
    }
  } else if (manufacturer === _.lowerCase(INSULET)) {
    siteChangeSource = SITE_CHANGE_RESERVOIR;
  }

  return siteChangeSource;
}

/**
 * Get the device-specific label for the site change source
 * @param {String} siteChangeSource
 * @param {String} manufacturer
 */
export function getSiteChangeSourceLabel(siteChangeSource, manufacturer) {
  const fallbackSubtitle = siteChangeSource !== SITE_CHANGE_TYPE_UNDECLARED
    ? pumpVocabulary.default[SITE_CHANGE_RESERVOIR]
    : null;

  return _.get(
    pumpVocabulary,
    [_.upperFirst(manufacturer), siteChangeSource],
    fallbackSubtitle,
  );
}

/**
 * Set the availability of basics sections
 *
 * @export
 * @param {Object} Provided data with empty sections disabled and empty text statements provided
 */
export function processBasicsAggregations(aggregations, data, patient, manufacturer) {
  /* eslint-disable no-param-reassign, max-len */
  const aggregationData = _.cloneDeep(data);

  const hasDataInRange = processedData => (
    processedData && (_.keys(processedData.byDate).length > 0)
  );

  const diabetesDataTypes = [
    'basals',
    'boluses',
  ];

  const getEmptyText = (aggregation, aggregationKey) => {
    /* eslint-disable max-len */
    let emptyText;

    switch (aggregationKey) {
      case 'basals':
      case 'boluses':
        emptyText = t("This section requires data from an insulin pump, so there's nothing to display.");
        break;

      case 'siteChanges':
        emptyText = hasDataInRange(aggregationData[aggregationKey])
          ? t("Please choose a preferred site change source from the 'Basics' web view to view this data.")
          : t("This section requires data from an insulin pump, so there's nothing to display.");
        break;

      case 'fingersticks':
        emptyText = t("This section requires data from a blood-glucose meter, so there's nothing to display.");
        break;

      default:
        emptyText = t('Why is this grey? There is not enough data to show this statistic.');
        break;
    }

    return emptyText;
    /* eslint-enable max-len */
  };

  _.each(aggregations, (aggregation, key) => {
    const type = aggregation.type;
    let disabled = false;

    if (_.includes(diabetesDataTypes, type)) {
      disabled = !hasDataInRange(aggregationData[type]);
    } else if (type === 'fingersticks') {
      const hasSMBG = hasDataInRange(aggregationData[type].smbg);
      const hasCalibrations = hasDataInRange(aggregationData[type].calibration);
      disabled = !hasSMBG && !hasCalibrations;
    } else if (type === 'siteChanges') {
      aggregations[key].source = getSiteChangeSource(patient, manufacturer);
      aggregations[key].manufacturer = manufacturer;
      disabled = aggregations[key].source === SITE_CHANGE_TYPE_UNDECLARED;
      if (!disabled) {
        aggregations[key].subTitle = getSiteChangeSourceLabel(
          aggregations[key].source,
          manufacturer
        );
      }
    }

    if (disabled) {
      aggregations[key].emptyText = getEmptyText(aggregation, key);
    }

    aggregations[key].disabled = disabled;
  });
  /* eslint-enable no-param-reassign */

  return aggregations;
}

/**
 * Get a keyed list of dates in range, designated as future, past, or most recent
 * @param {Array} range - The start and end points (Zulu timestamp or integer hammertime)
 * @param {String} timezone - A valid timezone, UTC if undefined
 * @returns {Object} Map of objects keyed by date
 */
export function findBasicsDays(range, timezone = 'UTC') {
  const days = [];
  let currentDate = moment.utc(range[0]).tz(timezone).toDate();
  const dateOfUpload = moment.utc(range[1]).tz(timezone).format('YYYY-MM-DD');
  while (currentDate < moment.utc(range[1]).tz(timezone).endOf('isoWeek')) {
    const date = moment.utc(currentDate).tz(timezone).format('YYYY-MM-DD');
    const dateObj = { date };
    if (date < dateOfUpload) {
      dateObj.type = 'past';
    } else if (date === dateOfUpload) {
      dateObj.type = 'mostRecent';
    } else {
      dateObj.type = 'future';
    }
    days.push(dateObj);
    currentDate = moment.utc(currentDate).tz(timezone).add(1, 'days').toDate();
  }
  return days;
}

/**
 * Find the appropriate start endpoint for basics calendars given the timestamp of the latest datum
 * @param {String} timestamp - Zulu timestamp (Integer hammertime also OK)
 * @param {String} timezone - A valid timezone, UTC if undefined
 * @returns {String} ISO date string relative to provided timezone
 */
export function findBasicsStart(timestamp, timezone = 'UTC') {
  return moment.utc(Date.parse(timestamp)).tz(timezone)
    .startOf('isoWeek')
    .subtract(14, 'days')
    .toDate();
}

/**
 * basicsText
 * @param  {Object} patient - the patient object that contains the profile
 * @param  {Object} data - DataUtil data object
 * @param  {Array} stats - Processed stats array
 * @param  {Object} stats - Processed aggregations object
 *
 * @return {String} Basics data as a formatted string
 */
export function basicsText(patient, data, stats, aggregations) {
  const {
    data: {
      aggregationsByDate = {},
      current: {
        endpoints = {},
      },
    },
    bgPrefs,
    timePrefs,
  } = data;

  const textUtil = new utils.TextUtil(patient, endpoints.range, timePrefs);

  let basicsString = textUtil.buildDocumentHeader('Basics');
  basicsString += textUtil.buildDocumentDates();
  basicsString += utils.statsText(stats, textUtil, bgPrefs);

  const getSummaryTableData = (dimensions, statData, header) => {
    const rows = [];
    const columns = [
      { key: 'label', label: 'Label' },
      { key: 'value', label: 'Value' },
    ];

    _.each(dimensions, dimension => {
      let value = _.get(statData, [...dimension.path.split('.'), dimension.key], {});

      value = dimension.average
        ? Math.round(_.get(statData, [...dimension.path.split('.'), 'avgPerDay']))
        : _.get(value, 'count', value);

      const stat = {
        label: dimension.label,
        value: (value || 0).toString(),
      };

      if (dimension.primary) {
        stat.label = header;
        rows.unshift(stat);
      } else {
        if (value === 0 && dimension.hideEmpty) {
          return;
        }
        rows.push(stat);
      }
    });

    return { rows, columns };
  };

  const getSiteChangesTableData = (infusionSiteData, siteChangeSource) => {
    const rows = [];
    const columns = [
      { key: 'label', label: 'Label' },
      { key: 'value', label: 'Value' },
    ];

    _.each(_.valuesIn(infusionSiteData), datum => {
      const daysSince = _.get(datum, ['summary', 'daysSince', siteChangeSource]);
      if (daysSince) rows.push(daysSince);
    });

    return {
      columns,
      rows: [
        { label: 'Mean Duration', value: `${Math.round(_.mean(rows) * 10) / 10} days` },
        { label: 'Longest Duration', value: `${_.max(rows)} days` },
      ],
    };
  };

  if (!aggregations.fingersticks.disabled) {
    const fingersticks = getSummaryTableData(
      aggregations.fingersticks.dimensions,
      aggregationsByDate.fingersticks,
      aggregations.fingersticks.summaryTitle
    );

    basicsString += textUtil.buildTextTable(
      '',
      fingersticks.rows,
      fingersticks.columns,
      { showHeader: false }
    );
  }

  if (!aggregations.boluses.disabled) {
    const boluses = getSummaryTableData(
      aggregations.boluses.dimensions,
      aggregationsByDate.boluses,
      aggregations.boluses.summaryTitle
    );

    basicsString += textUtil.buildTextTable(
      '',
      boluses.rows,
      boluses.columns,
      { showHeader: false }
    );
  }

  if (!aggregations.siteChanges.disabled) {
    const siteChanges = getSiteChangesTableData(
      _.get(aggregationsByDate, 'siteChanges.byDate', {}),
      aggregations.siteChanges.source,
    );

    basicsString += textUtil.buildTextTable(
      `${aggregations.siteChanges.title} from '${aggregations.siteChanges.subTitle}'`,
      siteChanges.rows,
      siteChanges.columns,
      { showHeader: false }
    );
  }

  if (!aggregations.basals.disabled) {
    const basals = getSummaryTableData(
      aggregations.basals.dimensions,
      aggregationsByDate.basals,
      aggregations.basals.summaryTitle
    );

    basicsString += textUtil.buildTextTable(
      '',
      basals.rows,
      basals.columns,
      { showHeader: false }
    );
  }

  return basicsString;
}
<commit_msg>[RELEASE-1.11.0] Fix most recent basics day calc<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

import _ from 'lodash';
import moment from 'moment';
import i18next from 'i18next';

import { getPumpVocabulary } from '../device';
import {
  generateBgRangeLabels,
  reshapeBgClassesToBgBounds,
} from '../bloodglucose';

import {
  SITE_CHANGE_RESERVOIR,
  SITE_CHANGE_TUBING,
  SITE_CHANGE_CANNULA,
  SITE_CHANGE_TYPE_UNDECLARED,
  AUTOMATED_DELIVERY,
  INSULET,
  TANDEM,
  ANIMAS,
  MEDTRONIC,
  pumpVocabulary,
} from '../constants';

import TextUtil from '../text/TextUtil';
import { statsText } from '../stat';

const t = i18next.t.bind(i18next);

// Exporting utils for easy stubbing in tests
export const utils = {
  reshapeBgClassesToBgBounds,
  statsText,
  TextUtil,
};

/**
 * Define sections and dimensions used in the basics view
 *
 * @param {Object} bgPrefs - bgPrefs object containing viz-style bgBounds
 * @returns {Object} sections
 */
export function defineBasicsAggregations(bgPrefs, manufacturer) {
  const bgLabels = generateBgRangeLabels(bgPrefs);
  bgLabels.veryLow = _.upperFirst(bgLabels.veryLow);
  bgLabels.veryHigh = _.upperFirst(bgLabels.veryHigh);

  const deviceLabels = getPumpVocabulary(manufacturer);

  const sectionNames = [
    'basals',
    'boluses',
    'fingersticks',
    'siteChanges',
  ];

  const sections = {};

  _.each(sectionNames, section => {
    let type = section;
    let dimensions;
    let title = '';
    let summaryTitle;
    let emptyText;
    const active = true;

    switch (section) {
      case 'basals':
        title = 'Basals';
        summaryTitle = t('Total basal events');
        dimensions = [
          { path: 'summary', key: 'total', label: t('Basal Events'), primary: true },
          { path: 'summary.subtotals', key: 'temp', label: t('Temp Basals') },
          { path: 'summary.subtotals', key: 'suspend', label: t('Suspends') },
          {
            path: 'summary.subtotals',
            key: 'automatedStop',
            label: t('{{automatedLabel}} Exited', {
              automatedLabel: deviceLabels[AUTOMATED_DELIVERY],
            }),
            hideEmpty: true,
          },
        ];
        break;

      case 'boluses':
        title = t('Bolusing');
        summaryTitle = t('Avg boluses / day');
        dimensions = [
          { path: 'summary', key: 'total', label: t('Avg per day'), average: true, primary: true },
          { path: 'summary.subtotals', key: 'wizard', label: t('Calculator'), percentage: true, selectorIndex: 0 },
          { path: 'summary.subtotals', key: 'correction', label: t('Correction'), percentage: true, selectorIndex: 1 },
          { path: 'summary.subtotals', key: 'extended', label: t('Extended'), percentage: true, selectorIndex: 3 },
          { path: 'summary.subtotals', key: 'interrupted', label: t('Interrupted'), percentage: true, selectorIndex: 4 },
          { path: 'summary.subtotals', key: 'override', label: t('Override'), percentage: true, selectorIndex: 2 },
          { path: 'summary.subtotals', key: 'underride', label: t('Underride'), percentage: true, selectorIndex: 5 },
        ];
        break;

      case 'fingersticks':
        title = t('BG readings');
        summaryTitle = t('Avg BG readings / day');
        dimensions = [
          { path: 'smbg.summary', key: 'total', label: t('Avg per day'), average: true, primary: true },
          { path: 'smbg.summary.subtotals', key: 'meter', label: t('Meter'), percentage: true },
          { path: 'smbg.summary.subtotals', key: 'manual', label: t('Manual'), percentage: true },
          { path: 'calibration.summary.subtotals', key: 'calibration', label: t('Calibrations'), hideEmpty: true },
          { path: 'smbg.summary.subtotals', key: 'veryLow', label: bgLabels.veryLow, percentage: true },
          { path: 'smbg.summary.subtotals', key: 'veryHigh', label: bgLabels.veryHigh, percentage: true },
        ];
        break;

      case 'siteChanges':
        title = t('Infusion site changes');
        break;

      default:
        type = false;
        break;
    }

    sections[section] = {
      active,
      title,
      summaryTitle,
      emptyText,
      type,
      dimensions,
    };
  });

  return sections;
}

/**
 * Generate the day labels based on the days supplied by the processed basics view data
 *
 * @export
 * @param {Array} days - supplied by the processed basics view data
 * @returns {Array} labels - formatted day labels.  I.E. [Mon, Tues, Wed, ...]
 */
export function generateCalendarDayLabels(days) {
  const firstDay = moment.utc(days[0].date).day();

  return _.map(_.range(firstDay, firstDay + 7), dow => (
    moment.utc().day(dow).format('ddd')
  ));
}

/**
 * Determine the site change source for the patient
 * @param {Object} patient
 * @param {String} manufacturer
 */
export function getSiteChangeSource(patient = {}, manufacturer) {
  const {
    settings,
  } = patient;

  let siteChangeSource = SITE_CHANGE_TYPE_UNDECLARED;

  if (_.includes(_.map([ANIMAS, MEDTRONIC, TANDEM], _.lowerCase), manufacturer)) {
    siteChangeSource = _.get(settings, 'siteChangeSource');
    const allowedSources = [SITE_CHANGE_CANNULA, SITE_CHANGE_TUBING];

    if (!_.includes(allowedSources, siteChangeSource)) {
      siteChangeSource = SITE_CHANGE_TYPE_UNDECLARED;
    }
  } else if (manufacturer === _.lowerCase(INSULET)) {
    siteChangeSource = SITE_CHANGE_RESERVOIR;
  }

  return siteChangeSource;
}

/**
 * Get the device-specific label for the site change source
 * @param {String} siteChangeSource
 * @param {String} manufacturer
 */
export function getSiteChangeSourceLabel(siteChangeSource, manufacturer) {
  const fallbackSubtitle = siteChangeSource !== SITE_CHANGE_TYPE_UNDECLARED
    ? pumpVocabulary.default[SITE_CHANGE_RESERVOIR]
    : null;

  return _.get(
    pumpVocabulary,
    [_.upperFirst(manufacturer), siteChangeSource],
    fallbackSubtitle,
  );
}

/**
 * Set the availability of basics sections
 *
 * @export
 * @param {Object} Provided data with empty sections disabled and empty text statements provided
 */
export function processBasicsAggregations(aggregations, data, patient, manufacturer) {
  /* eslint-disable no-param-reassign, max-len */
  const aggregationData = _.cloneDeep(data);

  const hasDataInRange = processedData => (
    processedData && (_.keys(processedData.byDate).length > 0)
  );

  const diabetesDataTypes = [
    'basals',
    'boluses',
  ];

  const getEmptyText = (aggregation, aggregationKey) => {
    /* eslint-disable max-len */
    let emptyText;

    switch (aggregationKey) {
      case 'basals':
      case 'boluses':
        emptyText = t("This section requires data from an insulin pump, so there's nothing to display.");
        break;

      case 'siteChanges':
        emptyText = hasDataInRange(aggregationData[aggregationKey])
          ? t("Please choose a preferred site change source from the 'Basics' web view to view this data.")
          : t("This section requires data from an insulin pump, so there's nothing to display.");
        break;

      case 'fingersticks':
        emptyText = t("This section requires data from a blood-glucose meter, so there's nothing to display.");
        break;

      default:
        emptyText = t('Why is this grey? There is not enough data to show this statistic.');
        break;
    }

    return emptyText;
    /* eslint-enable max-len */
  };

  _.each(aggregations, (aggregation, key) => {
    const type = aggregation.type;
    let disabled = false;

    if (_.includes(diabetesDataTypes, type)) {
      disabled = !hasDataInRange(aggregationData[type]);
    } else if (type === 'fingersticks') {
      const hasSMBG = hasDataInRange(aggregationData[type].smbg);
      const hasCalibrations = hasDataInRange(aggregationData[type].calibration);
      disabled = !hasSMBG && !hasCalibrations;
    } else if (type === 'siteChanges') {
      aggregations[key].source = getSiteChangeSource(patient, manufacturer);
      aggregations[key].manufacturer = manufacturer;
      disabled = aggregations[key].source === SITE_CHANGE_TYPE_UNDECLARED;
      if (!disabled) {
        aggregations[key].subTitle = getSiteChangeSourceLabel(
          aggregations[key].source,
          manufacturer
        );
      }
    }

    if (disabled) {
      aggregations[key].emptyText = getEmptyText(aggregation, key);
    }

    aggregations[key].disabled = disabled;
  });
  /* eslint-enable no-param-reassign */

  return aggregations;
}

/**
 * Get a keyed list of dates in range, designated as future, past, or most recent
 * @param {Array} range - The start and end points (Zulu timestamp or integer hammertime)
 * @param {String} timezone - A valid timezone, UTC if undefined
 * @returns {Object} Map of objects keyed by date
 */
export function findBasicsDays(range, timezone = 'UTC') {
  const days = [];
  let currentDate = moment.utc(range[0]).tz(timezone).toDate();
  const dateOfUpload = moment.utc(range[1]).subtract(1, 'ms').tz(timezone).format('YYYY-MM-DD');
  while (currentDate < moment.utc(range[1]).subtract(1, 'ms').tz(timezone).endOf('isoWeek')) {
    const date = moment.utc(currentDate).tz(timezone).format('YYYY-MM-DD');
    const dateObj = { date };
    if (date < dateOfUpload) {
      dateObj.type = 'past';
    } else if (date === dateOfUpload) {
      dateObj.type = 'mostRecent';
    } else {
      dateObj.type = 'future';
    }
    days.push(dateObj);
    currentDate = moment.utc(currentDate).tz(timezone).add(1, 'days').toDate();
  }
  return days;
}

/**
 * Find the appropriate start endpoint for basics calendars given the timestamp of the latest datum
 * @param {String} timestamp - Zulu timestamp (Integer hammertime also OK)
 * @param {String} timezone - A valid timezone, UTC if undefined
 * @returns {String} ISO date string relative to provided timezone
 */
export function findBasicsStart(timestamp, timezone = 'UTC') {
  return moment.utc(Date.parse(timestamp)).tz(timezone)
    .startOf('isoWeek')
    .subtract(14, 'days')
    .toDate();
}

/**
 * basicsText
 * @param  {Object} patient - the patient object that contains the profile
 * @param  {Object} data - DataUtil data object
 * @param  {Array} stats - Processed stats array
 * @param  {Object} stats - Processed aggregations object
 *
 * @return {String} Basics data as a formatted string
 */
export function basicsText(patient, data, stats, aggregations) {
  const {
    data: {
      aggregationsByDate = {},
      current: {
        endpoints = {},
      },
    },
    bgPrefs,
    timePrefs,
  } = data;

  const textUtil = new utils.TextUtil(patient, endpoints.range, timePrefs);

  let basicsString = textUtil.buildDocumentHeader('Basics');
  basicsString += textUtil.buildDocumentDates();
  basicsString += utils.statsText(stats, textUtil, bgPrefs);

  const getSummaryTableData = (dimensions, statData, header) => {
    const rows = [];
    const columns = [
      { key: 'label', label: 'Label' },
      { key: 'value', label: 'Value' },
    ];

    _.each(dimensions, dimension => {
      let value = _.get(statData, [...dimension.path.split('.'), dimension.key], {});

      value = dimension.average
        ? Math.round(_.get(statData, [...dimension.path.split('.'), 'avgPerDay']))
        : _.get(value, 'count', value);

      const stat = {
        label: dimension.label,
        value: (value || 0).toString(),
      };

      if (dimension.primary) {
        stat.label = header;
        rows.unshift(stat);
      } else {
        if (value === 0 && dimension.hideEmpty) {
          return;
        }
        rows.push(stat);
      }
    });

    return { rows, columns };
  };

  const getSiteChangesTableData = (infusionSiteData, siteChangeSource) => {
    const rows = [];
    const columns = [
      { key: 'label', label: 'Label' },
      { key: 'value', label: 'Value' },
    ];

    _.each(_.valuesIn(infusionSiteData), datum => {
      const daysSince = _.get(datum, ['summary', 'daysSince', siteChangeSource]);
      if (daysSince) rows.push(daysSince);
    });

    return {
      columns,
      rows: [
        { label: 'Mean Duration', value: `${Math.round(_.mean(rows) * 10) / 10} days` },
        { label: 'Longest Duration', value: `${_.max(rows)} days` },
      ],
    };
  };

  if (!aggregations.fingersticks.disabled) {
    const fingersticks = getSummaryTableData(
      aggregations.fingersticks.dimensions,
      aggregationsByDate.fingersticks,
      aggregations.fingersticks.summaryTitle
    );

    basicsString += textUtil.buildTextTable(
      '',
      fingersticks.rows,
      fingersticks.columns,
      { showHeader: false }
    );
  }

  if (!aggregations.boluses.disabled) {
    const boluses = getSummaryTableData(
      aggregations.boluses.dimensions,
      aggregationsByDate.boluses,
      aggregations.boluses.summaryTitle
    );

    basicsString += textUtil.buildTextTable(
      '',
      boluses.rows,
      boluses.columns,
      { showHeader: false }
    );
  }

  if (!aggregations.siteChanges.disabled) {
    const siteChanges = getSiteChangesTableData(
      _.get(aggregationsByDate, 'siteChanges.byDate', {}),
      aggregations.siteChanges.source,
    );

    basicsString += textUtil.buildTextTable(
      `${aggregations.siteChanges.title} from '${aggregations.siteChanges.subTitle}'`,
      siteChanges.rows,
      siteChanges.columns,
      { showHeader: false }
    );
  }

  if (!aggregations.basals.disabled) {
    const basals = getSummaryTableData(
      aggregations.basals.dimensions,
      aggregationsByDate.basals,
      aggregations.basals.summaryTitle
    );

    basicsString += textUtil.buildTextTable(
      '',
      basals.rows,
      basals.columns,
      { showHeader: false }
    );
  }

  return basicsString;
}
<|endoftext|>


<commit_before>import React, { useMemo } from "react";
import { useHotkeys } from "@blueprintjs/core";
import { startCase } from "lodash";

// This has been mostly superseded by blueprint's KeyCombo component, but may
// still be useful for cases where we need plain text
export function comboToLabel(def, useSymbols = true) {
  const combo = typeof def === "string" ? def : def.combo;

  if (useSymbols) {
    let parts = combo.replace("++", "+plus").split("+");
    parts = parts.map(p => symbols[p] || startCase(p) || p);
    return parts.join("");
  } else {
    return combo
      .split("+")
      .map(p => startCase(p) || p)
      .join(" + ")
      .replace("Meta", isMac ? "Cmd" : "Ctrl")
      .replace("Mod", isMac ? "Cmd" : "Ctrl")
      .replace("Alt", isMac ? "Option" : "Alt");
  }
}

// HOF to get hotkey combos by id
export const hotkeysById = (hotkeys, mode = "raw") => id => {
  const def = getHotkeyProps(hotkeys[id]);
  return (
    def &&
    (mode === "raw" ? def.combo : comboToLabel(def.combo, mode === "symbols"))
  );
};

// Translate shorthand array if needed
export const getHotkeyProps = (def, id) => {
  let out;
  if (typeof def === "string") {
    out = { combo: def };
  } else if (def instanceof Array) {
    out = { combo: def[0], label: def[1], ...(def[2] || {}) };
  } else {
    out = def;
  }
  out.label = out.label || startCase(id);
  return out;
};

/*
 * HOC to add hotkey support to components. Use this instead of blueprint's one.
 *
 * Arguments:
 * - hotkeySpec: either a named hotkey section previously registered, or an
 *   object mapping command ids to hotkey definitions, where each hotkey can
 *   be either:
 *   - a string consisting in the key combo (e.g. 'ctrl+shift+x')
 *   - an array holding the combo, label, and an object with any other props
 *   - an object holding all props
 * - handlers: an object mapping command ids to handler functions
 * - options: an object that may specify the follownig options:
 *   - functional: boolean indicating if the wrapped component will be a
 *     functional stateless component instead of a class-based one
 *
 * Returns a function that can be invoked with a component class, or a
 * stateless component function (if specified in the options) and returns
 * the decorated class. It may also be invoked without arguments to generate a
 * dummy ad-hoc component with no output.
 *
 */
export const withHotkeys = (hotkeys, handlers) => {
  return ({ children } = {}) => {
    const memoedHotkeys = useMemo(
      () =>
        Object.keys(hotkeys).map(id => {
          const { ...props } = getHotkeyProps(hotkeys[id], id);
          return {
            key: id,
            global: props.global !== false,
            onKeyDown: function(e) {
              return handlers[id](e);
            },
            ...props
          };
        }),
      []
    );

    const { handleKeyDown, handleKeyUp } = useHotkeys(memoedHotkeys);
    return (
      <div
        className="hotkeyHandler"
        tabIndex={0}
        {...{ onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }}
      >
        {children}
      </div>
    );
  };
};

const isMac = navigator.userAgent.includes("Mac OS X");

// TODO maybe avoid using symbols by default when not on Mac?
// Anyway, alternative 'Key + Key' description is provided as well
const symbols = {
  cmd: "⌘",
  meta: "⌘",
  ctrl: "⌃",
  alt: "⌥",
  shift: "⇧",
  esc: "␛", //'⎋',
  enter: "⏎",
  backspace: "⌫",
  plus: "+",
  tab: "⇥",
  space: "␣",
  capslock: "⇪",
  pageup: "⇞",
  pagedown: "⇟",
  home: "↖",
  end: "↘",
  left: "←",
  right: "→",
  up: "↑",
  down: "↓"
};

symbols.mod = symbols[isMac ? "meta" : "ctrl"];
<commit_msg>using cloneElement to no have to pass along an extra div when using withHotkeys<commit_after>import React, { useMemo } from "react";
import { useHotkeys } from "@blueprintjs/core";
import { startCase } from "lodash";

// This has been mostly superseded by blueprint's KeyCombo component, but may
// still be useful for cases where we need plain text
export function comboToLabel(def, useSymbols = true) {
  const combo = typeof def === "string" ? def : def.combo;

  if (useSymbols) {
    let parts = combo.replace("++", "+plus").split("+");
    parts = parts.map(p => symbols[p] || startCase(p) || p);
    return parts.join("");
  } else {
    return combo
      .split("+")
      .map(p => startCase(p) || p)
      .join(" + ")
      .replace("Meta", isMac ? "Cmd" : "Ctrl")
      .replace("Mod", isMac ? "Cmd" : "Ctrl")
      .replace("Alt", isMac ? "Option" : "Alt");
  }
}

// HOF to get hotkey combos by id
export const hotkeysById = (hotkeys, mode = "raw") => id => {
  const def = getHotkeyProps(hotkeys[id]);
  return (
    def &&
    (mode === "raw" ? def.combo : comboToLabel(def.combo, mode === "symbols"))
  );
};

// Translate shorthand array if needed
export const getHotkeyProps = (def, id) => {
  let out;
  if (typeof def === "string") {
    out = { combo: def };
  } else if (def instanceof Array) {
    out = { combo: def[0], label: def[1], ...(def[2] || {}) };
  } else {
    out = def;
  }
  out.label = out.label || startCase(id);
  return out;
};

/*
 * HOC to add hotkey support to components. Use this instead of blueprint's one.
 *
 * Arguments:
 * - hotkeySpec: either a named hotkey section previously registered, or an
 *   object mapping command ids to hotkey definitions, where each hotkey can
 *   be either:
 *   - a string consisting in the key combo (e.g. 'ctrl+shift+x')
 *   - an array holding the combo, label, and an object with any other props
 *   - an object holding all props
 * - handlers: an object mapping command ids to handler functions
 * - options: an object that may specify the follownig options:
 *   - functional: boolean indicating if the wrapped component will be a
 *     functional stateless component instead of a class-based one
 *
 * Returns a function that can be invoked with a component class, or a
 * stateless component function (if specified in the options) and returns
 * the decorated class. It may also be invoked without arguments to generate a
 * dummy ad-hoc component with no output.
 *
 */
export const withHotkeys = (hotkeys, handlers) => {
  return ({ children } = {}) => {
    const memoedHotkeys = useMemo(
      () =>
        Object.keys(hotkeys).map(id => {
          const { ...props } = getHotkeyProps(hotkeys[id], id);
          return {
            key: id,
            global: props.global !== false,
            onKeyDown: function(e) {
              return handlers[id](e);
            },
            ...props
          };
        }),
      []
    );

    const { handleKeyDown, handleKeyUp } = useHotkeys(memoedHotkeys);
    return React.cloneElement(children, {
      tabIndex: 0,
      onKeyDown: handleKeyDown,
      onKeyUp: handleKeyUp
    });
  };
};

const isMac = navigator.userAgent.includes("Mac OS X");

// TODO maybe avoid using symbols by default when not on Mac?
// Anyway, alternative 'Key + Key' description is provided as well
const symbols = {
  cmd: "⌘",
  meta: "⌘",
  ctrl: "⌃",
  alt: "⌥",
  shift: "⇧",
  esc: "␛", //'⎋',
  enter: "⏎",
  backspace: "⌫",
  plus: "+",
  tab: "⇥",
  space: "␣",
  capslock: "⇪",
  pageup: "⇞",
  pagedown: "⇟",
  home: "↖",
  end: "↘",
  left: "←",
  right: "→",
  up: "↑",
  down: "↓"
};

symbols.mod = symbols[isMac ? "meta" : "ctrl"];
<|endoftext|>


<commit_before>import Vue from 'vue';
import merge from 'element-ui/src/utils/merge';
import PopupManager from 'element-ui/src/utils/popup/popup-manager';
import getScrollBarWidth from '../scrollbar-width';

let idSeed = 1;
const transitions = [];

const hookTransition = (transition) => {
  if (transitions.indexOf(transition) !== -1) return;

  const getVueInstance = (element) => {
    let instance = element.__vue__;
    if (!instance) {
      const textNode = element.previousSibling;
      if (textNode.__vue__) {
        instance = textNode.__vue__;
      }
    }
    return instance;
  };

  Vue.transition(transition, {
    afterEnter(el) {
      const instance = getVueInstance(el);

      if (instance) {
        instance.doAfterOpen && instance.doAfterOpen();
      }
    },
    afterLeave(el) {
      const instance = getVueInstance(el);

      if (instance) {
        instance.doAfterClose && instance.doAfterClose();
      }
    }
  });
};

let scrollBarWidth;

const getDOM = function(dom) {
  if (dom.nodeType === 3) {
    dom = dom.nextElementSibling || dom.nextSibling;
    getDOM(dom);
  }
  return dom;
};

export default {
  props: {
    value: {
      type: Boolean,
      default: false
    },
    transition: {
      type: String,
      default: ''
    },
    openDelay: {},
    closeDelay: {},
    zIndex: {},
    modal: {
      type: Boolean,
      default: false
    },
    modalFade: {
      type: Boolean,
      default: true
    },
    modalClass: {},
    modalAppendToBody: {
      type: Boolean,
      default: false
    },
    lockScroll: {
      type: Boolean,
      default: true
    },
    closeOnPressEscape: {
      type: Boolean,
      default: false
    },
    closeOnClickModal: {
      type: Boolean,
      default: false
    }
  },

  created() {
    if (this.transition) {
      hookTransition(this.transition);
    }
  },

  beforeMount() {
    this._popupId = 'popup-' + idSeed++;
    PopupManager.register(this._popupId, this);
  },

  beforeDestroy() {
    PopupManager.deregister(this._popupId);
    PopupManager.closeModal(this._popupId);
    if (this.modal && this.bodyOverflow !== null && this.bodyOverflow !== 'hidden') {
      document.body.style.overflow = this.bodyOverflow;
      document.body.style.paddingRight = this.bodyPaddingRight;
    }
    this.bodyOverflow = null;
    this.bodyPaddingRight = null;
  },

  data() {
    return {
      opened: false,
      bodyOverflow: null,
      bodyPaddingRight: null,
      rendered: false
    };
  },

  watch: {
    value(val) {
      if (val) {
        if (this._opening) return;
        if (!this.rendered) {
          this.rendered = true;
          Vue.nextTick(() => {
            this.open();
          });
        } else {
          this.open();
        }
      } else {
        this.close();
      }
    }
  },

  methods: {
    open(options) {
      if (!this.rendered) {
        this.rendered = true;
        this.$emit('input', true);
      }

      const props = merge({}, this, options);

      if (this._closeTimer) {
        clearTimeout(this._closeTimer);
        this._closeTimer = null;
      }
      clearTimeout(this._openTimer);

      const openDelay = Number(props.openDelay);
      if (openDelay > 0) {
        this._openTimer = setTimeout(() => {
          this._openTimer = null;
          this.doOpen(props);
        }, openDelay);
      } else {
        this.doOpen(props);
      }
    },

    doOpen(props) {
      if (this.$isServer) return;
      if (this.willOpen && !this.willOpen()) return;
      if (this.opened) return;

      this._opening = true;

      // 使用 vue-popup 的组件，如果需要和父组件通信显示的状态，应该使用 value，它是一个 prop，
      // 这样在父组件中用 v-model 即可；否则可以使用 visible，它是一个 data
      this.visible = true;
      this.$emit('input', true);

      const dom = getDOM(this.$el);

      const modal = props.modal;

      const zIndex = props.zIndex;
      if (zIndex) {
        PopupManager.zIndex = zIndex;
      }

      if (modal) {
        if (this._closing) {
          PopupManager.closeModal(this._popupId);
          this._closing = false;
        }
        PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), this.modalAppendToBody ? undefined : dom, props.modalClass, props.modalFade);
        if (props.lockScroll) {
          if (!this.bodyOverflow) {
            this.bodyPaddingRight = document.body.style.paddingRight;
            this.bodyOverflow = document.body.style.overflow;
          }
          scrollBarWidth = getScrollBarWidth();
          let bodyHasOverflow = document.documentElement.clientHeight < document.body.scrollHeight;
          if (scrollBarWidth > 0 && bodyHasOverflow) {
            document.body.style.paddingRight = scrollBarWidth + 'px';
          }
          document.body.style.overflow = 'hidden';
        }
      }

      if (getComputedStyle(dom).position === 'static') {
        dom.style.position = 'absolute';
      }

      dom.style.zIndex = PopupManager.nextZIndex();
      this.opened = true;

      this.onOpen && this.onOpen();

      if (!this.transition) {
        this.doAfterOpen();
      }
    },

    doAfterOpen() {
      this._opening = false;
    },

    close() {
      if (this.willClose && !this.willClose()) return;

      if (this._openTimer !== null) {
        clearTimeout(this._openTimer);
        this._openTimer = null;
      }
      clearTimeout(this._closeTimer);

      const closeDelay = Number(this.closeDelay);

      if (closeDelay > 0) {
        this._closeTimer = setTimeout(() => {
          this._closeTimer = null;
          this.doClose();
        }, closeDelay);
      } else {
        this.doClose();
      }
    },

    doClose() {
      this.visible = false;
      this.$emit('input', false);
      this._closing = true;

      this.onClose && this.onClose();

      if (this.lockScroll) {
        setTimeout(() => {
          if (this.modal && this.bodyOverflow !== 'hidden') {
            document.body.style.overflow = this.bodyOverflow;
            document.body.style.paddingRight = this.bodyPaddingRight;
          }
          this.bodyOverflow = null;
          this.bodyPaddingRight = null;
        }, 200);
      }

      this.opened = false;

      if (!this.transition) {
        this.doAfterClose();
      }
    },

    doAfterClose() {
      PopupManager.closeModal(this._popupId);
      this._closing = false;
    }
  }
};

export {
  PopupManager
};
<commit_msg>Popup: prepare for the upcoming change of Vue 2.2.0 (#3067)<commit_after>import Vue from 'vue';
import merge from 'element-ui/src/utils/merge';
import PopupManager from 'element-ui/src/utils/popup/popup-manager';
import getScrollBarWidth from '../scrollbar-width';

let idSeed = 1;
const transitions = [];

const hookTransition = (transition) => {
  if (transitions.indexOf(transition) !== -1) return;

  const getVueInstance = (element) => {
    let instance = element.__vue__;
    if (!instance) {
      const textNode = element.previousSibling;
      if (textNode.__vue__) {
        instance = textNode.__vue__;
      }
    }
    return instance;
  };

  Vue.transition(transition, {
    afterEnter(el) {
      const instance = getVueInstance(el);

      if (instance) {
        instance.doAfterOpen && instance.doAfterOpen();
      }
    },
    afterLeave(el) {
      const instance = getVueInstance(el);

      if (instance) {
        instance.doAfterClose && instance.doAfterClose();
      }
    }
  });
};

let scrollBarWidth;

const getDOM = function(dom) {
  if (dom.nodeType === 3) {
    dom = dom.nextElementSibling || dom.nextSibling;
    getDOM(dom);
  }
  return dom;
};

export default {
  props: {
    value: {
      type: Boolean,
      default: false
    },
    transition: {
      type: String,
      default: ''
    },
    openDelay: {},
    closeDelay: {},
    zIndex: {},
    modal: {
      type: Boolean,
      default: false
    },
    modalFade: {
      type: Boolean,
      default: true
    },
    modalClass: {},
    modalAppendToBody: {
      type: Boolean,
      default: false
    },
    lockScroll: {
      type: Boolean,
      default: true
    },
    closeOnPressEscape: {
      type: Boolean,
      default: false
    },
    closeOnClickModal: {
      type: Boolean,
      default: false
    }
  },

  created() {
    if (this.transition) {
      hookTransition(this.transition);
    }
  },

  beforeMount() {
    this._popupId = 'popup-' + idSeed++;
    PopupManager.register(this._popupId, this);
  },

  beforeDestroy() {
    PopupManager.deregister(this._popupId);
    PopupManager.closeModal(this._popupId);
    if (this.modal && this.bodyOverflow !== null && this.bodyOverflow !== 'hidden') {
      document.body.style.overflow = this.bodyOverflow;
      document.body.style.paddingRight = this.bodyPaddingRight;
    }
    this.bodyOverflow = null;
    this.bodyPaddingRight = null;
  },

  data() {
    return {
      opened: false,
      bodyOverflow: null,
      bodyPaddingRight: null,
      rendered: false
    };
  },

  watch: {
    value(val) {
      if (val) {
        if (this._opening) return;
        if (!this.rendered) {
          this.rendered = true;
          Vue.nextTick(() => {
            this.open();
          });
        } else {
          this.open();
        }
      } else {
        this.close();
      }
    }
  },

  methods: {
    open(options) {
      if (!this.rendered) {
        this.rendered = true;
        this.$emit('input', true);
      }

      const props = merge({}, this.$props || this, options);

      if (this._closeTimer) {
        clearTimeout(this._closeTimer);
        this._closeTimer = null;
      }
      clearTimeout(this._openTimer);

      const openDelay = Number(props.openDelay);
      if (openDelay > 0) {
        this._openTimer = setTimeout(() => {
          this._openTimer = null;
          this.doOpen(props);
        }, openDelay);
      } else {
        this.doOpen(props);
      }
    },

    doOpen(props) {
      if (this.$isServer) return;
      if (this.willOpen && !this.willOpen()) return;
      if (this.opened) return;

      this._opening = true;

      // 使用 vue-popup 的组件，如果需要和父组件通信显示的状态，应该使用 value，它是一个 prop，
      // 这样在父组件中用 v-model 即可；否则可以使用 visible，它是一个 data
      this.visible = true;
      this.$emit('input', true);

      const dom = getDOM(this.$el);

      const modal = props.modal;

      const zIndex = props.zIndex;
      if (zIndex) {
        PopupManager.zIndex = zIndex;
      }

      if (modal) {
        if (this._closing) {
          PopupManager.closeModal(this._popupId);
          this._closing = false;
        }
        PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), this.modalAppendToBody ? undefined : dom, props.modalClass, props.modalFade);
        if (props.lockScroll) {
          if (!this.bodyOverflow) {
            this.bodyPaddingRight = document.body.style.paddingRight;
            this.bodyOverflow = document.body.style.overflow;
          }
          scrollBarWidth = getScrollBarWidth();
          let bodyHasOverflow = document.documentElement.clientHeight < document.body.scrollHeight;
          if (scrollBarWidth > 0 && bodyHasOverflow) {
            document.body.style.paddingRight = scrollBarWidth + 'px';
          }
          document.body.style.overflow = 'hidden';
        }
      }

      if (getComputedStyle(dom).position === 'static') {
        dom.style.position = 'absolute';
      }

      dom.style.zIndex = PopupManager.nextZIndex();
      this.opened = true;

      this.onOpen && this.onOpen();

      if (!this.transition) {
        this.doAfterOpen();
      }
    },

    doAfterOpen() {
      this._opening = false;
    },

    close() {
      if (this.willClose && !this.willClose()) return;

      if (this._openTimer !== null) {
        clearTimeout(this._openTimer);
        this._openTimer = null;
      }
      clearTimeout(this._closeTimer);

      const closeDelay = Number(this.closeDelay);

      if (closeDelay > 0) {
        this._closeTimer = setTimeout(() => {
          this._closeTimer = null;
          this.doClose();
        }, closeDelay);
      } else {
        this.doClose();
      }
    },

    doClose() {
      this.visible = false;
      this.$emit('input', false);
      this._closing = true;

      this.onClose && this.onClose();

      if (this.lockScroll) {
        setTimeout(() => {
          if (this.modal && this.bodyOverflow !== 'hidden') {
            document.body.style.overflow = this.bodyOverflow;
            document.body.style.paddingRight = this.bodyPaddingRight;
          }
          this.bodyOverflow = null;
          this.bodyPaddingRight = null;
        }, 200);
      }

      this.opened = false;

      if (!this.transition) {
        this.doAfterClose();
      }
    },

    doAfterClose() {
      PopupManager.closeModal(this._popupId);
      this._closing = false;
    }
  }
};

export {
  PopupManager
};
<|endoftext|>


<commit_before>
/**
 * Description goes here
 */
export default class InputHandler {
  /**
   * Description goes here
   *
   * @param {Phaser.Game} param.game - Current game
   * @param {MapListener} param.mapListener - Current map listener
   * @param {CameraMover} param.cameraMover - Current camera mover
   */
  constructor({ game, mapListener, cameraMover, mapView }) {
    this.game = game
    this.mapListener = mapListener
    this.cameraMover = cameraMover
    this.mapView = mapView

    this.initialize()
  }

  /**
   * add callbacks to controls -> Phaser takes care of update in game loop
   */
  initialize () {
    this.game.input.onDown.add(this.onPointerDown, this)
    var cursors = this.game.cursors
    cursors.up.onDown.add(this.onCursorDown, this)
    cursors.down.onDown.add(this.onCursorDown, this)
    cursors.left.onDown.add(this.onCursorDown, this)
    cursors.right.onDown.add(this.onCursorDown, this)
    // let daisiesKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR)
    let flowersKey = this.game.flowersKey
    flowersKey.onDown.add(this.flowersOnOff, this)
    this.kineticScrolling()
  }

  /**
   * Description goes here
   */
  onPointerDown () {
    var ptr = this.game.input.activePointer
    var pointerEvent = {
      x: ptr.position.x,
      y: ptr.position.y
    }

    this.mapListener.update(pointerEvent)
  }

  /**
   * Description goes here
   */
  onCursorDown () {
    var cursors = this.game.cursors
    var cursorEvent = {
      up: cursors.up.isDown,
      down: cursors.down.isDown,
      left: cursors.left.isDown,
      right: cursors.right.isDown
    }

    this.cameraMover.update({ cursor: cursorEvent })
  }

  flowersOnOff () {
    this.mapView.showFlowers === true
      ? this.mapView.showFlowers = false
      : this.mapView.showFlowers = true
  }

  kineticScrolling () {
    // Configuration
    this.game.game.kineticScrolling.configure({
      kineticMovement: true,
      timeConstantScroll: 325, //really mimic iOS
      horizontalScroll: true,
      verticalScroll: true,
      horizontalWheel: false,
      verticalWheel: false,
      deltaWheel: 40
    })

    // Override function so that pointer on menu area does not register as movement
    let context = this.mapListener
    this.game.game.kineticScrolling.beginMove = function () {
      this.startX = this.game.input.x;
      this.startY = this.game.input.y;
      if (context.pointerInMapArea({ x: this.startX, y: this.startY })) {
        this.pressedDown = true;
      }
      this.timestamp = Date.now();
      this.velocityY = this.amplitudeY = this.velocityX = this.amplitudeX = 0;
    }, context;

    // Start scrolling = adds callbacks to game
    this.game.game.kineticScrolling.start();
  }
}
<commit_msg>eslint-herjoja pois<commit_after>
/**
 * Description goes here
 */
export default class InputHandler {
  /**
   * Description goes here
   *
   * @param {Phaser.Game} param.game - Current game
   * @param {MapListener} param.mapListener - Current map listener
   * @param {CameraMover} param.cameraMover - Current camera mover
   */
  constructor ({ game, mapListener, cameraMover, mapView }) {
    this.game = game
    this.mapListener = mapListener
    this.cameraMover = cameraMover
    this.mapView = mapView

    this.initialize()
  }

  /**
   * add callbacks to controls -> Phaser takes care of update in game loop
   */
  initialize () {
    this.game.input.onDown.add(this.onPointerDown, this)
    var cursors = this.game.cursors
    cursors.up.onDown.add(this.onCursorDown, this)
    cursors.down.onDown.add(this.onCursorDown, this)
    cursors.left.onDown.add(this.onCursorDown, this)
    cursors.right.onDown.add(this.onCursorDown, this)
    // let daisiesKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR)
    let flowersKey = this.game.flowersKey
    flowersKey.onDown.add(this.flowersOnOff, this)
    this.kineticScrolling()
  }

  /**
   * Description goes here
   */
  onPointerDown () {
    var ptr = this.game.input.activePointer
    var pointerEvent = {
      x: ptr.position.x,
      y: ptr.position.y
    }

    this.mapListener.update(pointerEvent)
  }

  /**
   * Description goes here
   */
  onCursorDown () {
    var cursors = this.game.cursors
    var cursorEvent = {
      up: cursors.up.isDown,
      down: cursors.down.isDown,
      left: cursors.left.isDown,
      right: cursors.right.isDown
    }

    this.cameraMover.update({ cursor: cursorEvent })
  }

  flowersOnOff () {
    this.mapView.showFlowers === true
      ? this.mapView.showFlowers = false
      : this.mapView.showFlowers = true
  }

  kineticScrolling () {
    // Configuration
    this.game.game.kineticScrolling.configure({
      kineticMovement: true,
      timeConstantScroll: 325, // really mimic iOS
      horizontalScroll: true,
      verticalScroll: true,
      horizontalWheel: false,
      verticalWheel: false,
      deltaWheel: 40
    })

    // Override function so that pointer on menu area does not register as movement
    let context = this.mapListener
    this.game.game.kineticScrolling.beginMove = function () {
      this.startX = this.game.input.x
      this.startY = this.game.input.y
      if (context.pointerInMapArea({ x: this.startX, y: this.startY })) {
        this.pressedDown = true
      }
      this.timestamp = Date.now()
      this.velocityY = this.amplitudeY = this.velocityX = this.amplitudeX = 0
    }, context;

    // Start scrolling = adds callbacks to game
    this.game.game.kineticScrolling.start()
  }
}
<|endoftext|>


<commit_before>const $ = require('jquery');
const _ = require('lodash');
const Firebase = require('firebase');
const actions = require('../actions');
const leafStore = require('../stores/leafs');
const bundleStore = require('../stores/bundles');

class LeafBrowser {

  constructor($el) {
    this.$el = $el;
    this.render();
  }

  render() {
    this.$el.html(`

    `);
    let $bundle = this.$el.find('.top-bundle');
    this.$el.find('.add-bundle').on('click', (e) => {
      this.promptForNewBundle($bundle, null);
    });

    // we need to recursively build for each bundle
    // and each bundle's children bundles

    // I'll want to avoid rerendering this whole thing on each update
    bundleStore.listen((bundles) => {
      let $childrenContainer = this.$el.find('.top-bundle-children');
      $childrenContainer.empty();

      // first filter bundles at the top node
      let topBundles = _.filter(bundles, (b) => b.parent === undefined);
      // then recursively build the dom
      this.renderBundlesIntoEl(topBundles, $childrenContainer);
    });
  }

  // recursive function
  renderBundlesIntoEl(bundles, $el) {
    _.each(bundles, (bundle) => {
      let $bundle = $(`

${ bundle.name }

      `);
      let $children = $bundle.find('.bundle-children');
      $bundle.find('.add-bundle').on('click', (e) => {
        this.promptForNewBundle($bundle, bundle.id);
      });
      $el.append($bundle);
      $bundle.children('.bundle-label').on('click', (e) => {
        this.toggleBundle($bundle)
      });
      let children = bundleStore.getBundlesByIds(bundle.bundles);
      this.renderBundlesIntoEl(children, $children);
    });
  }

  toggleBundle($bundle) {
    $bundle.toggleClass('expanded').toggleClass('collapsed');
  }

  promptForNewBundle($parentBundle, parentBundleId) {
    let $dom = $(`

        <input type='text' class='new-bundle-prompt'>

    `);
    let $input = $dom.find('input');
    $parentBundle.children('.bundle-children').prepend($dom);
    $input.focus();
    $input.on('keydown', (e) => {
      if (e.which === 13) {
        $dom.remove();
        actions.createBundle({ name: $input.val() }, parentBundleId);
      }
    });
  }

}

module.exports = LeafBrowser;<commit_msg>make escaping input prompts easier in leafbrowser<commit_after>const $ = require('jquery');
const _ = require('lodash');
const Firebase = require('firebase');
const actions = require('../actions');
const leafStore = require('../stores/leafs');
const bundleStore = require('../stores/bundles');

class LeafBrowser {

  constructor($el) {
    this.$el = $el;
    this.render();
  }

  render() {
    this.$el.html(`

    `);
    let $bundle = this.$el.find('.top-bundle');
    this.$el.find('.add-bundle').on('click', (e) => {
      this.promptForNewBundle($bundle, null);
    });

    // we need to recursively build for each bundle
    // and each bundle's children bundles

    // I'll want to avoid rerendering this whole thing on each update
    bundleStore.listen((bundles) => {
      let $childrenContainer = this.$el.find('.top-bundle-children');
      $childrenContainer.empty();

      // first filter bundles at the top node
      let topBundles = _.filter(bundles, (b) => b.parent === undefined);
      // then recursively build the dom
      this.renderBundlesIntoEl(topBundles, $childrenContainer);
    });
  }

  // recursive function
  renderBundlesIntoEl(bundles, $el) {
    _.each(bundles, (bundle) => {
      let $bundle = $(`

${ bundle.name }

      `);
      let $children = $bundle.find('.bundle-children');
      $bundle.find('.add-bundle').on('click', (e) => {
        this.promptForNewBundle($bundle, bundle.id);
      });
      $el.append($bundle);
      $bundle.children('.bundle-label').on('click', (e) => {
        this.toggleBundle($bundle)
      });
      let children = bundleStore.getBundlesByIds(bundle.bundles);
      this.renderBundlesIntoEl(children, $children);
    });
  }

  toggleBundle($bundle) {
    $bundle.toggleClass('expanded').toggleClass('collapsed');
  }

  promptForNewBundle($parentBundle, parentBundleId) {
    let $dom = $(`

        <input type='text' class='new-bundle-prompt'>

    `);
    let $input = $dom.find('input');
    $parentBundle.children('.bundle-children').prepend($dom);
    $input.focus();
    $input.on('keydown', (e) => {
      if (e.which === 13) { // enter
        $dom.remove();
        actions.createBundle({ name: $input.val() }, parentBundleId);
      } else if (e.which === 27) { // esc
        $dom.remove();
      }
    });
    $input.on('blur', (e) => $dom.remove());
  }

}

module.exports = LeafBrowser;<|endoftext|>


<commit_before>'use strict';

var handlebars = require('handlebars')
    , exec
    , dateformat = require('dateformat')
    , crypto = require('crypto')
    , async = require('async')
    , grunt = require('grunt')
    , fs = require('fs')
    , mkdirp = require('mkdirp')
    , file = grunt.file
    , log = grunt.log;

var cwd = __dirname;

var createFile = function (template, options) {
    var outerMetadata = file.read(cwd + '/../template/' + template);
    var template = handlebars.compile(outerMetadata);
    var metadata = template(options);
    return metadata;
};

var md5 = function (str) {
    var hash = crypto.createHash('md5');
    return hash.update(str).digest('hex');
};

var sha1 = function (str) {
    var hash = crypto.createHash('sha1');
    return hash.update(str).digest('hex');
};

var save = function (fileContent, pomDir, fileName) {
    file.write(pomDir + '/' + fileName, fileContent);
    file.write(pomDir + '/' + fileName + '.md5', md5(fileContent));
    file.write(pomDir + '/' + fileName + '.sha1', sha1(fileContent));
};

var directoryExists = function(dir) {
    try {
        return fs.statSync(dir).isDirectory();
    } catch (e) {
        // error is thrown by statSync when path does not exist
        if (e.code === 'ENOENT') {
            return false
        }
        throw e;
    }
};

var isFunction = function(obj) {
    return !!(obj && obj.constructor && obj.call && obj.apply);
};

var createAndUploadArtifacts = function (options, done) {
    var pomDir = options.pomDir || 'test/poms';

    options.parallel = options.parallel === undefined ? false : options.parallel;
    if (!directoryExists(pomDir)) {
        mkdirp(pomDir);
    }

    save(createFile('project-metadata.xml', options), pomDir, 'outer.xml');
    save(createFile('latest-metadata.xml', options), pomDir, 'inner.xml');
    save(createFile('pom.xml', options), pomDir, 'pom.xml');

    var artifact = isFunction(options.artifact) ? options.artifact() : options.artifact;
    var artifactData = file.read(artifact, {encoding: null});
    file.write(pomDir + '/artifact.' + options.packaging + '.md5', md5(artifactData));
    file.write(pomDir + '/artifact.' + options.packaging + '.sha1', sha1(artifactData));

    var upload = function (fileLocation, targetFile) {
        var uploadArtifact = function (cb) {
            var targetUri = options.url + '/' + targetFile, status;
            if (!options.quiet) {
                log.write('Uploading to ' + targetUri + "\n\n");
            }

            var curlOptions = [
                '--silent',
                '--fail',
                '--show-error',
                '--write-out', '"%{http_code}"',
                '--upload-file', fileLocation,
                '--noproxy', options.noproxy ? options.noproxy : '127.0.0.1'
            ];

            if (options.auth) {
                curlOptions.push('-u');
                var authString = options.auth.username + ':' + options.auth.password;
                authString.replace(/'/g, '\\\'');
                curlOptions.push("'" + authString + "'");
            }

            if (options.insecure) {
                curlOptions.push('--insecure');
            }

            var execOptions = {};
            options.cwd && (execOptions.cwd = options.cwd);

            var curlCmd = ['curl', curlOptions.join(' '), targetUri].join(' ');

            var childProcess = exec(curlCmd, execOptions, function () {
            });
            childProcess.stdout.on('data', function (data) {
                status = data;
            });
            childProcess.on('close', function (code) {
                if (code !== 0 || (status !== "200" && status !== "201")) {
                    cb("Status code " + status + " for " + targetUri, null);
                } else {
                    cb(null, "Ok");
                }
            });
        };
        return uploadArtifact;
    };

    var uploads = {};

    var groupIdAsPath = options.groupId.replace(/\./g, "/");
    var groupArtifactPath = groupIdAsPath + '/' + options.artifactId;

    uploads[pomDir + "/outer.xml"] = groupArtifactPath + '/' + 'maven-metadata.xml';
    uploads[pomDir + "/outer.xml.sha1"] = groupArtifactPath + '/' + 'maven-metadata.xml.sha1';
    uploads[pomDir + "/outer.xml.md5"] = groupArtifactPath + '/' + 'maven-metadata.xml.md5';

    var SNAPSHOT_VER = /.*SNAPSHOT$/i;

    var version = isFunction(options.version) ? options.version() : options.version;
    var groupArtifactVersionPath = groupArtifactPath + '/' + version;
    if (SNAPSHOT_VER.test(version)) {
        uploads[pomDir + "/inner.xml"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml';
        uploads[pomDir + "/inner.xml.sha1"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.sha1';
        uploads[pomDir + "/inner.xml.md5"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.md5';
    }

    var remoteArtifactName = options.artifactId + '-' + version;
    uploads[pomDir + "/pom.xml"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom';
    uploads[pomDir + "/pom.xml.sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.sha1';
    uploads[pomDir + "/pom.xml.md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.md5';

    if(options.classifier) {
        remoteArtifactName = remoteArtifactName + "-" + options.classifier;
    }
    uploads[artifact] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging;
    uploads[pomDir + "/artifact." + options.packaging + ".sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.sha1';
    uploads[pomDir + "/artifact." + options.packaging + ".md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.md5';

    var fns = [];
    for (var u in uploads) {
        if (uploads.hasOwnProperty(u)) {
            fns.push(upload(u, uploads[u]));
        }
    }

    var asyncFn = options.parallel ? async.parallel : async.series;
    asyncFn(fns, function (err) {
        if (!options.quiet) {
            log.write('-------------------------------------------\n');
            if (err) {
                log.error('Artifact Upload failed\n' + String(err));
            } else {
                log.ok('Artifacts uploaded successfully');
            }
        }
        done(err ? false : true);
    });

};

module.exports = function (options, cb) {
    if (!options) {
        throw {name: "IllegalArgumentException", message: "upload artifact options required."};
    }
    exec = process.env.MOCK_NEXUS ? require('./mockexec') : require('child_process').exec;
    options.lastUpdated = process.env.MOCK_NEXUS ? '11111111111111': dateformat(new Date(), "yyyymmddHHMMss", true);
    createAndUploadArtifacts(options, cb);
};
<commit_msg>- Fixed CURL credentials bug on Windows (Single quotes are invalid) - Added error message when CURL is not found (or other exec error occurs)<commit_after>'use strict';

var handlebars = require('handlebars')
    , exec
    , dateformat = require('dateformat')
    , crypto = require('crypto')
    , async = require('async')
    , grunt = require('grunt')
    , fs = require('fs')
    , mkdirp = require('mkdirp')
    , file = grunt.file
    , log = grunt.log;

var cwd = __dirname;

var createFile = function (template, options) {
    var outerMetadata = file.read(cwd + '/../template/' + template);
    var template = handlebars.compile(outerMetadata);
    var metadata = template(options);
    return metadata;
};

var md5 = function (str) {
    var hash = crypto.createHash('md5');
    return hash.update(str).digest('hex');
};

var sha1 = function (str) {
    var hash = crypto.createHash('sha1');
    return hash.update(str).digest('hex');
};

var save = function (fileContent, pomDir, fileName) {
    file.write(pomDir + '/' + fileName, fileContent);
    file.write(pomDir + '/' + fileName + '.md5', md5(fileContent));
    file.write(pomDir + '/' + fileName + '.sha1', sha1(fileContent));
};

var directoryExists = function(dir) {
    try {
        return fs.statSync(dir).isDirectory();
    } catch (e) {
        // error is thrown by statSync when path does not exist
        if (e.code === 'ENOENT') {
            return false
        }
        throw e;
    }
};

var isFunction = function(obj) {
    return !!(obj && obj.constructor && obj.call && obj.apply);
};

var createAndUploadArtifacts = function (options, done) {
    var pomDir = options.pomDir || 'test/poms';

    options.parallel = options.parallel === undefined ? false : options.parallel;
    if (!directoryExists(pomDir)) {
        mkdirp(pomDir);
    }

    save(createFile('project-metadata.xml', options), pomDir, 'outer.xml');
    save(createFile('latest-metadata.xml', options), pomDir, 'inner.xml');
    save(createFile('pom.xml', options), pomDir, 'pom.xml');

    var artifact = isFunction(options.artifact) ? options.artifact() : options.artifact;
    var artifactData = file.read(artifact, {encoding: null});
    file.write(pomDir + '/artifact.' + options.packaging + '.md5', md5(artifactData));
    file.write(pomDir + '/artifact.' + options.packaging + '.sha1', sha1(artifactData));

    var upload = function (fileLocation, targetFile) {
        var uploadArtifact = function (cb) {
            var targetUri = options.url + '/' + targetFile, status;
            if (!options.quiet) {
                log.write('Uploading to ' + targetUri + "\n\n");
            }

            var curlOptions = [
                '--silent',
                '--fail',
                '--show-error',
                '--write-out', '"%{http_code}"',
                '--upload-file', fileLocation,
                '--noproxy', options.noproxy ? options.noproxy : '127.0.0.1'
            ];

            if (options.auth) {
                curlOptions.push('-u');
                var authString = options.auth.username + ':' + options.auth.password;
                authString.replace(/'/g, '\\\'');
                curlOptions.push("\"" + authString + "\"");
            }

            if (options.insecure) {
                curlOptions.push('--insecure');
            }

            var execOptions = {};
            options.cwd && (execOptions.cwd = options.cwd);

            var curlCmd = ['curl', curlOptions.join(' '), targetUri].join(' ');

            var childProcess = exec(curlCmd, execOptions, function (error) {
                if (error) {
                    log.error(error);
                }
            });
            childProcess.stdout.on('data', function (data) {
                status = data;
            });
            childProcess.on('close', function (code) {
                if (code !== 0 || (status !== "200" && status !== "201")) {
                    cb("Status code " + status + " for " + targetUri, null);
                } else {
                    cb(null, "Ok");
                }
            });
        };
        return uploadArtifact;
    };

    var uploads = {};

    var groupIdAsPath = options.groupId.replace(/\./g, "/");
    var groupArtifactPath = groupIdAsPath + '/' + options.artifactId;

    uploads[pomDir + "/outer.xml"] = groupArtifactPath + '/' + 'maven-metadata.xml';
    uploads[pomDir + "/outer.xml.sha1"] = groupArtifactPath + '/' + 'maven-metadata.xml.sha1';
    uploads[pomDir + "/outer.xml.md5"] = groupArtifactPath + '/' + 'maven-metadata.xml.md5';

    var SNAPSHOT_VER = /.*SNAPSHOT$/i;

    var version = isFunction(options.version) ? options.version() : options.version;
    var groupArtifactVersionPath = groupArtifactPath + '/' + version;
    if (SNAPSHOT_VER.test(version)) {
        uploads[pomDir + "/inner.xml"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml';
        uploads[pomDir + "/inner.xml.sha1"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.sha1';
        uploads[pomDir + "/inner.xml.md5"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.md5';
    }

    var remoteArtifactName = options.artifactId + '-' + version;
    uploads[pomDir + "/pom.xml"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom';
    uploads[pomDir + "/pom.xml.sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.sha1';
    uploads[pomDir + "/pom.xml.md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.md5';

    if(options.classifier) {
        remoteArtifactName = remoteArtifactName + "-" + options.classifier;
    }
    uploads[artifact] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging;
    uploads[pomDir + "/artifact." + options.packaging + ".sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.sha1';
    uploads[pomDir + "/artifact." + options.packaging + ".md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.md5';

    var fns = [];
    for (var u in uploads) {
        if (uploads.hasOwnProperty(u)) {
            fns.push(upload(u, uploads[u]));
        }
    }

    var asyncFn = options.parallel ? async.parallel : async.series;
    asyncFn(fns, function (err) {
        if (!options.quiet) {
            log.write('-------------------------------------------\n');
            if (err) {
                log.error('Artifact Upload failed\n' + String(err));
            } else {
                log.ok('Artifacts uploaded successfully');
            }
        }
        done(err ? false : true);
    });

};

module.exports = function (options, cb) {
    if (!options) {
        throw {name: "IllegalArgumentException", message: "upload artifact options required."};
    }
    exec = process.env.MOCK_NEXUS ? require('./mockexec') : require('child_process').exec;
    options.lastUpdated = process.env.MOCK_NEXUS ? '11111111111111': dateformat(new Date(), "yyyymmddHHMMss", true);
    createAndUploadArtifacts(options, cb);
};
<|endoftext|>


<commit_before>module.exports = {
  options: {
    timeout: 1000,
    reporter: 'mocha-multi',
    reporterOptions: {
      spec: '-',
    },
  },
  all: ['dist/tests.js'],
};

if (process.env.COVERAGE === 'y') {
  var path = require('path');

  var coverageReporterModulePath = path.resolve(
    path.join(__dirname, '..', 'tests', 'coverage-reporter.js')
  );

  module.exports.options.reporterOptions[coverageReporterModulePath] = '-';
}
<commit_msg>fix(): Increased test timeout for Travis<commit_after>module.exports = {
  options: {
    timeout: 2000,
    reporter: 'mocha-multi',
    reporterOptions: {
      spec: '-',
    },
  },
  all: ['dist/tests.js'],
};

if (process.env.COVERAGE === 'y') {
  var path = require('path');

  var coverageReporterModulePath = path.resolve(
    path.join(__dirname, '..', 'tests', 'coverage-reporter.js')
  );

  module.exports.options.reporterOptions[coverageReporterModulePath] = '-';
}
<|endoftext|>


<commit_before>angular.module('app.services').service('HLShortcuts', HLShortcuts);

HLShortcuts.$inject = ['$state', '$timeout', '$rootScope', 'Settings'];
function HLShortcuts($state, $timeout, $rootScope, Settings) {
    // Use $rootScope because $root is unavailable in services.
    // This watches the change in states so we can execute certain Mousetrap
    // functions on specific pages.
    $rootScope.$on('$stateChangeSuccess', function() {
        var state = $state.current.name;
        if (state === 'base.email.list') {
            Mousetrap.bind('c', function() {
                $state.go('base.email.compose');
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('c');
            });
        }

        if (state === 'base.email.detail') {
            Mousetrap.bind('c', function() {
                _replyViaShortcode();
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('c');
            });

            Mousetrap.bind('r', function() {
                _replyViaShortcode();
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('r');
            });

            Mousetrap.bind('d', function() {
                // Broadcast event for email/controllers/detail.js to handle
                // sending the particular email to trash.
                $rootScope.$broadcast('deleteMessageByShortCode');
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('d');
            });

            Mousetrap.bind('e', function() {
                // Broadcast event for email/controllers/detail.js to handle
                // sending the particular email to trash.
                $rootScope.$broadcast('archiveMessageByShortCode');
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('e');
            });
        }

        if (state === 'base.email.compose') {
            Mousetrap.bind('mod+enter', function() {
                // Submit the email form.
                document.getElementById('fileupload').submit();
            });
        } else if (state === 'base.deals.detail.edit' || state === 'base.deals.create') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for deals/controllers/createupdate.js to save
                // the deal.
                $rootScope.$broadcast('saveDeal');
            }, 'keyup');
        } else if (state === 'base.cases.detail.edit' || state === 'base.cases.create') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for cases/controllers/createupdate.js to save
                // the case.
                $rootScope.$broadcast('saveCase');
            }, 'keyup');
        } else if (state === 'base.contacts.detail.edit' || state === 'base.contacts.create' ||
                   state === 'base.contacts.create.fromAccount') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for contact/controllers/createupdate.js to save
                // the contact.
                $rootScope.$broadcast('saveContact');
            });
        } else if (state === 'base.accounts.detail.edit' || state === 'base.accounts.create') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for account/controllers/createupdate.js to save
                // the account.
                $rootScope.$broadcast('saveAccount');
            });
        } else if (state === 'base.email.detail') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for account/controllers/createupdate.js to save
                // the account.
                if (Settings.email.sidebar.form === 'account') {
                    $rootScope.$broadcast('saveAccount');
                } else if (Settings.email.sidebar.form === 'contact') {
                    $rootScope.$broadcast('saveContact');
                }
            });
        } else {
            // Unbind when not a form page.
            Mousetrap.unbind('mod+enter');
        }

        // Function for both the c and r key when in Email Detail view.
        function _replyViaShortcode() {
            var message = $state.params.id;
            if (message) {
                $state.go('base.email.reply', {id: message});
            }
        }

        $timeout(function() {
            var searchField = '.hl-search-field';

            if (angular.element(searchField).length) {
                Mousetrap.bind('s', function() {
                    var scope = angular.element($(searchField).get(0)).scope();
                    angular.element(searchField).focus();
                    // TODO: Remove if statement if Contacts get refactored
                    // To utilize vm.table. instead of table.
                    if (state === 'base.contacts') {
                        scope.table.filter = '';

                        // TODO: Remove else if statement if we refactor code to
                        // make the filter/search model consistent.
                    } else if (state === 'base.deals' || state === 'base.cases') {
                        scope.vm.table.searchQuery = '';
                    } else {
                        scope.vm.table.filter = '';
                    }
                    scope.$apply();
                }, 'keyup');
            }
        });
    });
}
<commit_msg>Removed delete and archive shortcuts<commit_after>angular.module('app.services').service('HLShortcuts', HLShortcuts);

HLShortcuts.$inject = ['$state', '$timeout', '$rootScope', 'Settings'];
function HLShortcuts($state, $timeout, $rootScope, Settings) {
    // Use $rootScope because $root is unavailable in services.
    // This watches the change in states so we can execute certain Mousetrap
    // functions on specific pages.
    $rootScope.$on('$stateChangeSuccess', function() {
        var state = $state.current.name;
        if (state === 'base.email.list') {
            Mousetrap.bind('c', function() {
                $state.go('base.email.compose');
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('c');
            });
        }

        if (state === 'base.email.detail') {
            Mousetrap.bind('c', function() {
                _replyViaShortcode();
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('c');
            });

            Mousetrap.bind('r', function() {
                _replyViaShortcode();
                // Unbind after pressed so next bind can take place.
                Mousetrap.unbind('r');
            });
        }

        if (state === 'base.email.compose') {
            Mousetrap.bind('mod+enter', function() {
                // Submit the email form.
                document.getElementById('fileupload').submit();
            });
        } else if (state === 'base.deals.detail.edit' || state === 'base.deals.create') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for deals/controllers/createupdate.js to save
                // the deal.
                $rootScope.$broadcast('saveDeal');
            }, 'keyup');
        } else if (state === 'base.cases.detail.edit' || state === 'base.cases.create') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for cases/controllers/createupdate.js to save
                // the case.
                $rootScope.$broadcast('saveCase');
            }, 'keyup');
        } else if (state === 'base.contacts.detail.edit' || state === 'base.contacts.create' ||
                   state === 'base.contacts.create.fromAccount') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for contact/controllers/createupdate.js to save
                // the contact.
                $rootScope.$broadcast('saveContact');
            });
        } else if (state === 'base.accounts.detail.edit' || state === 'base.accounts.create') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for account/controllers/createupdate.js to save
                // the account.
                $rootScope.$broadcast('saveAccount');
            });
        } else if (state === 'base.email.detail') {
            Mousetrap.bindGlobal('mod+enter', function() {
                // Broadcast event for account/controllers/createupdate.js to save
                // the account.
                if (Settings.email.sidebar.form === 'account') {
                    $rootScope.$broadcast('saveAccount');
                } else if (Settings.email.sidebar.form === 'contact') {
                    $rootScope.$broadcast('saveContact');
                }
            });
        } else {
            // Unbind when not a form page.
            Mousetrap.unbind('mod+enter');
        }

        // Function for both the c and r key when in Email Detail view.
        function _replyViaShortcode() {
            var message = $state.params.id;
            if (message) {
                $state.go('base.email.reply', {id: message});
            }
        }

        $timeout(function() {
            var searchField = '.hl-search-field';

            if (angular.element(searchField).length) {
                Mousetrap.bind('s', function() {
                    var scope = angular.element($(searchField).get(0)).scope();
                    angular.element(searchField).focus();
                    // TODO: Remove if statement if Contacts get refactored
                    // To utilize vm.table. instead of table.
                    if (state === 'base.contacts') {
                        scope.table.filter = '';

                        // TODO: Remove else if statement if we refactor code to
                        // make the filter/search model consistent.
                    } else if (state === 'base.deals' || state === 'base.cases') {
                        scope.vm.table.searchQuery = '';
                    } else {
                        scope.vm.table.filter = '';
                    }
                    scope.$apply();
                }, 'keyup');
            }
        });
    });
}
<|endoftext|>