<commit_before>import React from 'react';

import Tab from 'Main/Tab';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Analyzer from 'Parser/Core/Analyzer';
import ResourceBreakdown from 'Parser/Core/Modules/ResourceTracker/ResourceBreakdown';
import { formatPercentage } from 'common/format';
import Icon from 'common/Icon';

import AstralPowerTracker from './AstralPowerTracker';

class AstralPowerDetails extends Analyzer {
  static dependencies = {
    astralPowerTracker: AstralPowerTracker,
  };

  get wasted() {
    return this.astralPowerTracker.wasted || 0;
  }

  get total() {
    return this.astralPowerTracker.wasted + this.astralPowerTracker.generated || 0;
  }

  get wastedPerMinute() {
    return (this.wasted / this.owner.fightDuration) * 1000 * 60;
  }

  get wastedPercent() {
    return this.wasted / this.total || 0;
  }

  get suggestionThresholds() {
    return {
      actual: 1 - this.wastedPercent,
      isLessThan: {
        minor: 1.00,
        average: 0.98,
        major: 0.95,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholds)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest('You overcapped {this.wasted} Astral Power. Always prioritize spending it over avoiding the overcap of any other ability.')
          .icon('ability_druid_cresentburn')
          .actual(`${formatPercentage(this.wastedPercentage)}% overcapped Astral Power`)
          .recommended(`<${formatPercentage(recommended)}% is recommended`);
      });
  }

  statistic() {
    return (
      <StatisticBox
        icon={<Icon icon="ability_druid_cresentburn" />}
        value={`${this.wastedPerMinute.toFixed(2)}`}
        label="Overcapped Astral Power per minute"
        tooltip={`${this.wasted} out of ${this.total} (${formatPercentage(this.wastedPercentage)}%) Astral Power wasted.`}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.CORE(1);

  tab() {
    return {
      title: 'Astral Power usage',
      url: 'astral-power-usage',
      render: () => (
        <Tab title="Astral Power usage breakdown">
          <ResourceBreakdown
            tracker={this.astralPowerTracker}
            showSpenders={true}
          />
        </Tab>
      ),
    };
 }

}

export default AstralPowerDetails;
<commit_msg>Fix AsP suggestion & tooltip<commit_after>import React from 'react';

import Tab from 'Main/Tab';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Analyzer from 'Parser/Core/Analyzer';
import ResourceBreakdown from 'Parser/Core/Modules/ResourceTracker/ResourceBreakdown';
import { formatPercentage } from 'common/format';
import Icon from 'common/Icon';

import AstralPowerTracker from './AstralPowerTracker';

class AstralPowerDetails extends Analyzer {
  static dependencies = {
    astralPowerTracker: AstralPowerTracker,
  };

  get wasted() {
    return this.astralPowerTracker.wasted || 0;
  }

  get total() {
    return this.astralPowerTracker.wasted + this.astralPowerTracker.generated || 0;
  }

  get wastedPerMinute() {
    return (this.wasted / this.owner.fightDuration) * 1000 * 60;
  }

  get wastedPercent() {
    return this.wasted / this.total || 0;
  }

  get suggestionThresholdsWasted() {
    return {
      actual: this.wastedPercent,
      isGreaterThan: {
        minor: 0,
        average: 0.02,
        major: 0.05,
      },
      style: 'percentage',
    };
  }

  get suggestionThresholds() {
    return {
      actual: 1 - this.wastedPercent,
      isLessThan: {
        minor: 1.00,
        average: 0.98,
        major: 0.95,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholdsWasted)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(`You overcapped ${this.wasted} Astral Power. Always prioritize spending it over avoiding the overcap of any other ability.`)
          .icon('ability_druid_cresentburn')
          .actual(`${formatPercentage(actual)}% overcapped Astral Power`)
          .recommended(`${formatPercentage(recommended)}% is recommended`);
      });
  }

  statistic() {
    return (
      <StatisticBox
        icon={<Icon icon="ability_druid_cresentburn" />}
        value={`${this.wastedPerMinute.toFixed(2)}`}
        label="Overcapped Astral Power per minute"
        tooltip={`${this.wasted} out of ${this.total} (${formatPercentage(this.wastedPercent)}%) Astral Power wasted.`}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.CORE(1);

  tab() {
    return {
      title: 'Astral Power usage',
      url: 'astral-power-usage',
      render: () => (
        <Tab title="Astral Power usage breakdown">
          <ResourceBreakdown
            tracker={this.astralPowerTracker}
            showSpenders={true}
          />
        </Tab>
      ),
    };
 }

}

export default AstralPowerDetails;
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');
var gutil = require('gulp-util');
var log = gutil.log;
var colors = gutil.colors;
var Timer = require('./timer');
var inject = require('./injector');
var makeContainerTemplate = require('./makecontainertemplate');
var makeComponentTemplate = require('./makecomponenttemplate');

/*

Need to create:
  - actions file
  - data file
  - handler file
  - container file
  - component file

Need to inject:
  - constants
  - reducers import
  - reducers key/val
  - initialState key/val

*/

function makeConstantTemplate(capitalized, allCaps) {
  return '/**\n'
       + ' * Constants for ' + capitalized + '-related items\n'
       + ' */\n'
       + 'export const' + allCaps + ' = objectize([\n'
       + "  'FOO'\n"
       + ']);';
}

function makeReducerImportTemplate(reducers, reducersImport) {
  return 'import ' + reducers + " from '" + reducersImport + "';";
}

function makeReducerKeyVal(name, reducers) {
  return name + ': ' + reducers + ',';
}

function makeStateKeyVal(name) {
  return name + ': {},';
}

function makeActionsTemplate(constants) {
  return "import { " + constants + " } from '../lib/constants';\n"
       + "\n"
       + "export function foo() {\n"
       + "  return {\n"
       + "    type: " + constants + ".FOO\n"
       + "  }\n"
       + "}\n";
}

function makeDataTemplate(constants) {
  return "import axios from 'axios';\n"
       + "import brightsocket from 'brightsocket.io-client';\n"
       + "import { " + constants + " } from '../lib/constants';\n"
       + "\n"
       + "export function foo() {\n"
       + "\n"
       + "}\n";
}

function makeHandlerTemplate() {
  return "export function handleFoo(evt) {\n"
       + "  evt.preventDefault();\n"
       + "}\n";
}

function makeJs(names, callback) {
  var timer = new Timer();
  timer.start();
  log('Starting', colors.cyan('new JavaScript layer') + '...');

  var actionsTemplate       = makeActionsTemplate(names.constants);
  var dataTemplate          = makeDataTemplate(names.constants);
  var handlerTemplate       = makeHandlerTemplate();
  var containerTemplate     = makeConstantTemplate(names);
  var componentTemplate     = makeComponentTemplate(names);
  var constantTemplate      = makeConstantTemplate(names.component, names.constants);
  var reducerImportTemplate = makeReducerImportTemplate(names.reducers, names.reducersImport);
  var reducerKeyValTemplate = makeReducerKeyVal(names.state, names.reducers);
  var stateKeyValTemplate   = makeStateKeyVal(names.state);

  var actionsPath    = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'actions', names.actionsFile);
  var dataPath       = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'data', names.dataFile);
  var handlersPath   = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'handlers', names.handlersFile);
  var containerPath  = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'containers', names.containerFile);
  var componentPath  = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'components', names.componentFile);
  var constantsIndex = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'lib', 'constants.js');
  var reducersPath   = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'reducers', names.reducersFile);
  var reducersIndex  = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'reducers', 'reducers.js');
  var stateIndex     = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'state', 'initialstate.js');

  try {
    fs.writeFileSync(actionsPath, actionsTemplate);
    log('Built', names.actionsFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(dataPath, dataTemplate);
    log('Built', names.dataFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(handlersPath, handlersTemplate);
    log('Built', names.handlersFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(containerPath, containerTemplate);
    log('Built', names.containerFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(componentPath, componentTemplate);
    log('Built', names.componentFile);
  } catch (err) {
    console.log(err);
  }

  log('Finished', colors.cyan('new JavaScript layer'), "after", colors.magenta(timer.end()));
  callback && callback();
}

module.exports = makeJs;
<commit_msg>fix bad ref<commit_after>var path = require('path');
var fs = require('fs');
var gutil = require('gulp-util');
var log = gutil.log;
var colors = gutil.colors;
var Timer = require('./timer');
var inject = require('./injector');
var makeContainerTemplate = require('./makecontainertemplate');
var makeComponentTemplate = require('./makecomponenttemplate');

/*

Need to create:
  - actions file
  - data file
  - handler file
  - container file
  - component file

Need to inject:
  - constants
  - reducers import
  - reducers key/val
  - initialState key/val

*/

function makeConstantTemplate(capitalized, allCaps) {
  return '/**\n'
       + ' * Constants for ' + capitalized + '-related items\n'
       + ' */\n'
       + 'export const' + allCaps + ' = objectize([\n'
       + "  'FOO'\n"
       + ']);';
}

function makeReducerImportTemplate(reducers, reducersImport) {
  return 'import ' + reducers + " from '" + reducersImport + "';";
}

function makeReducerKeyVal(name, reducers) {
  return name + ': ' + reducers + ',';
}

function makeStateKeyVal(name) {
  return name + ': {},';
}

function makeActionsTemplate(constants) {
  return "import { " + constants + " } from '../lib/constants';\n"
       + "\n"
       + "export function foo() {\n"
       + "  return {\n"
       + "    type: " + constants + ".FOO\n"
       + "  }\n"
       + "}\n";
}

function makeDataTemplate(constants) {
  return "import axios from 'axios';\n"
       + "import brightsocket from 'brightsocket.io-client';\n"
       + "import { " + constants + " } from '../lib/constants';\n"
       + "\n"
       + "export function foo() {\n"
       + "\n"
       + "}\n";
}

function makeHandlerTemplate() {
  return "export function handleFoo(evt) {\n"
       + "  evt.preventDefault();\n"
       + "}\n";
}

function makeJs(names, callback) {
  var timer = new Timer();
  timer.start();
  log('Starting', colors.cyan('new JavaScript layer') + '...');

  var actionsTemplate       = makeActionsTemplate(names.constants);
  var dataTemplate          = makeDataTemplate(names.constants);
  var handlersTemplate      = makeHandlerTemplate();
  var containerTemplate     = makeConstantTemplate(names);
  var componentTemplate     = makeComponentTemplate(names);
  var constantTemplate      = makeConstantTemplate(names.component, names.constants);
  var reducerImportTemplate = makeReducerImportTemplate(names.reducers, names.reducersImport);
  var reducerKeyValTemplate = makeReducerKeyVal(names.state, names.reducers);
  var stateKeyValTemplate   = makeStateKeyVal(names.state);

  var actionsPath    = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'actions', names.actionsFile);
  var dataPath       = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'data', names.dataFile);
  var handlersPath   = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'handlers', names.handlersFile);
  var containerPath  = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'containers', names.containerFile);
  var componentPath  = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'components', names.componentFile);
  var constantsIndex = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'lib', 'constants.js');
  var reducersPath   = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'reducers', names.reducersFile);
  var reducersIndex  = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'reducers', 'reducers.js');
  var stateIndex     = path.resolve(__dirname, '../', 'frontend', 'src', 'js', 'state', 'initialstate.js');

  try {
    fs.writeFileSync(actionsPath, actionsTemplate);
    log('Built', names.actionsFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(dataPath, dataTemplate);
    log('Built', names.dataFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(handlersPath, handlersTemplate);
    log('Built', names.handlersFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(containerPath, containerTemplate);
    log('Built', names.containerFile);
  } catch (err) {
    console.log(err);
  }

  try {
    fs.writeFileSync(componentPath, componentTemplate);
    log('Built', names.componentFile);
  } catch (err) {
    console.log(err);
  }

  log('Finished', colors.cyan('new JavaScript layer'), "after", colors.magenta(timer.end()));
  callback && callback();
}

module.exports = makeJs;
<|endoftext|>


<commit_before>import recordAnalyticsEvent from 'platform/monitoring/record-event';
import {
  createCNPDirectDepositAnalyticsDataObject,
  getData,
  isEligibleForCNPDirectDeposit,
  isSignedUpForCNPDirectDeposit,
  isSignedUpForEDUDirectDeposit,
} from '../util';

import { captureError, ERROR_SOURCES } from '../util/analytics';

export const CNP_PAYMENT_INFORMATION_FETCH_STARTED =
  'CNP_PAYMENT_INFORMATION_FETCH_STARTED';
export const CNP_PAYMENT_INFORMATION_FETCH_SUCCEEDED =
  'CNP_PAYMENT_INFORMATION_FETCH_SUCCEEDED';
export const CNP_PAYMENT_INFORMATION_FETCH_FAILED =
  'CNP_PAYMENT_INFORMATION_FETCH_FAILED';

export const CNP_PAYMENT_INFORMATION_EDIT_TOGGLED =
  'CNP_PAYMENT_INFORMATION_EDIT_TOGGLED';

export const CNP_PAYMENT_INFORMATION_SAVE_STARTED =
  'CNP_PAYMENT_INFORMATION_SAVE_STARTED';
export const CNP_PAYMENT_INFORMATION_SAVE_SUCCEEDED =
  'CNP_PAYMENT_INFORMATION_SAVE_SUCCEEDED';
export const CNP_PAYMENT_INFORMATION_SAVE_FAILED =
  'CNP_PAYMENT_INFORMATION_SAVE_FAILED';

export const EDU_PAYMENT_INFORMATION_FETCH_STARTED =
  'EDU_PAYMENT_INFORMATION_FETCH_STARTED';
export const EDU_PAYMENT_INFORMATION_FETCH_SUCCEEDED =
  'EDU_PAYMENT_INFORMATION_FETCH_SUCCEEDED';
export const EDU_PAYMENT_INFORMATION_FETCH_FAILED =
  'EDU_PAYMENT_INFORMATION_FETCH_FAILED';

export const EDU_PAYMENT_INFORMATION_EDIT_TOGGLED =
  'EDU_PAYMENT_INFORMATION_EDIT_TOGGLED';

export const EDU_PAYMENT_INFORMATION_SAVE_STARTED =
  'EDU_PAYMENT_INFORMATION_SAVE_STARTED';
export const EDU_PAYMENT_INFORMATION_SAVE_SUCCEEDED =
  'EDU_PAYMENT_INFORMATION_SAVE_SUCCEEDED';
export const EDU_PAYMENT_INFORMATION_SAVE_FAILED =
  'EDU_PAYMENT_INFORMATION_SAVE_FAILED';

const captureDirectDepositErrorResponse = ({ error, apiEventName }) => {
  const [firstError = {}] = error.errors ?? [];
  const {
    code = 'code-unknown',
    title = 'title-unknown',
    detail = 'detail-unknown',
    status = 'status-unknown',
  } = firstError;

  captureError(error, {
    eventName: apiEventName,
    code,
    title,
    detail,
    status,
  });
};

export function fetchCNPPaymentInformation(recordEvent = recordAnalyticsEvent) {
  return async dispatch => {
    dispatch({ type: CNP_PAYMENT_INFORMATION_FETCH_STARTED });

    recordEvent({ event: `profile-get-cnp-direct-deposit-started` });
    const response = await getData('/ppiu/payment_information');

    // sample error when getting payment information
    // response = {
    //   errors: [
    //     {
    //       title: 'Bad Gateway',
    //       detail: 'Received an an invalid response from the upstream server',
    //       code: 'EVSS502',
    //       source: 'EVSS::PPIU::Service',
    //       status: '502',
    //     },
    //   ],
    // };

    if (response.error) {
      recordEvent({ event: `profile-get-cnp-direct-deposit-failed` });
      dispatch({
        type: CNP_PAYMENT_INFORMATION_FETCH_FAILED,
        response,
      });
    } else {
      recordEvent({
        event: `profile-get-cnp-direct-deposit-retrieved`,
        // The API might report an empty payment address for some folks who are
        // already enrolled in direct deposit. But we want to make sure we
        // always treat those who are signed up as being eligible. Therefore
        // we'll check to see if they either have a payment address _or_ are
        // already signed up for direct deposit here:
        'direct-deposit-setup-eligible':
          isEligibleForCNPDirectDeposit(response) ||
          isSignedUpForCNPDirectDeposit(response),
        'direct-deposit-setup-complete': isSignedUpForCNPDirectDeposit(
          response,
        ),
      });
      dispatch({
        type: CNP_PAYMENT_INFORMATION_FETCH_SUCCEEDED,
        response,
      });
    }
  };
}

export function saveCNPPaymentInformation(
  fields,
  isEnrollingInDirectDeposit = false,
  recordEvent = recordAnalyticsEvent,
) {
  return async dispatch => {
    let gaClientId;
    try {
      // eslint-disable-next-line no-undef
      gaClientId = ga.getAll()[0].get('clientId');
    } catch (e) {
      // don't want to break submitting because of a weird GA issue
    }
    const apiRequestOptions = {
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...fields,
        gaClientId,
      }),
      method: 'PUT',
      mode: 'cors',
    };

    dispatch({ type: CNP_PAYMENT_INFORMATION_SAVE_STARTED });

    const response = await getData(
      '/ppiu/payment_information',
      apiRequestOptions,
    );

    // Leaving this here for the time being while we wrap up the error handling
    // const response = {
    //   error: {
    //     errors: [
    //       {
    //         title: 'Unprocessable Entity',
    //         detail: 'One or more unprocessable user payment properties',
    //         code: '126',
    //         source: 'EVSS::PPIU::Service',
    //         status: '422',
    //         meta: {
    //           messages: [
    //             {
    //               key: 'cnp.payment.generic.error.message',
    //               severity: 'ERROR',
    //               text:
    //                 'Generic CnP payment update error. Update response: Update Failed: Night area number is invalid, must be 3 digits',
    //             },
    //           ],
    //         },
    //       },
    //     ],
    //   },
    // };

    if (response.error || response.errors) {
      const errors = response.error?.errors || [];
      const analyticsData = createCNPDirectDepositAnalyticsDataObject(
        errors,
        isEnrollingInDirectDeposit,
      );
      recordEvent(analyticsData);
      dispatch({
        type: CNP_PAYMENT_INFORMATION_SAVE_FAILED,
        response,
      });
    } else {
      recordEvent({
        event: 'profile-transaction',
        'profile-section': `cnp-direct-deposit-information`,
      });
      dispatch({
        type: CNP_PAYMENT_INFORMATION_SAVE_SUCCEEDED,
        response,
      });
    }
  };
}

export function editCNPPaymentInformationToggled(open) {
  return { type: CNP_PAYMENT_INFORMATION_EDIT_TOGGLED, open };
}

export function fetchEDUPaymentInformation(recordEvent = recordAnalyticsEvent) {
  return async dispatch => {
    dispatch({ type: EDU_PAYMENT_INFORMATION_FETCH_STARTED });

    recordEvent({ event: 'profile-get-edu-direct-deposit-started' });
    try {
      const response = await getData('/profile/ch33_bank_accounts');
      // .errors is returned from the API, .error is returned from getData
      if (response.errors || response.error) {
        const err = response.error || response.errors;
        recordEvent({ event: 'profile-get-edu-direct-deposit-failed' });
        captureDirectDepositErrorResponse({
          error: { ...err, source: ERROR_SOURCES.API },
          apiEventName: 'profile-get-edu-direct-deposit-failed',
        });
        dispatch({
          type: EDU_PAYMENT_INFORMATION_FETCH_FAILED,
          response,
        });
      } else {
        recordEvent({
          event: 'profile-get-edu-direct-deposit-retrieved',
          // NOTE: the GET profile/ch33_bank_accounts/ is not able to tell us if a
          // user is eligible to set up DD for EDU, so we are only reporting if
          // they are currently enrolled in DD for EDU or not
          'direct-deposit-setup-complete': isSignedUpForEDUDirectDeposit(
            response,
          ),
        });
        dispatch({
          type: EDU_PAYMENT_INFORMATION_FETCH_SUCCEEDED,
          response: {
            paymentAccount: response,
          },
        });
      }
    } catch (error) {
      recordEvent({ event: 'profile-get-edu-direct-deposit-failed' });
      captureDirectDepositErrorResponse({
        error,
        apiEventName: 'profile-get-edu-direct-deposit-failed',
      });
      dispatch({
        type: EDU_PAYMENT_INFORMATION_FETCH_FAILED,
        response: { error },
      });
    }
  };
}

export function saveEDUPaymentInformation(
  fields,
  recordEvent = recordAnalyticsEvent,
) {
  return async dispatch => {
    let gaClientId;
    try {
      // eslint-disable-next-line no-undef
      gaClientId = ga.getAll()[0].get('clientId');
    } catch (e) {
      // don't want to break submitting because of a weird GA issue
    }
    const apiRequestOptions = {
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...fields,
        gaClientId,
      }),
      method: 'PUT',
      mode: 'cors',
    };

    dispatch({ type: EDU_PAYMENT_INFORMATION_SAVE_STARTED });
    try {
      const response = await getData(
        '/profile/ch33_bank_accounts',
        apiRequestOptions,
      );
      // .errors is returned from the API, .error is returned from getData
      if (response.errors || response.error) {
        const err = response.errors || response.error?.errors;
        let errorName = 'unknown';
        if (err) {
          if (err.length > 0) {
            errorName = err[0].title;
          } else {
            errorName = err?.title || 'unknown-title';
          }
        }
        recordEvent({
          event: 'profile-edit-failure',
          'profile-action': 'save-failure',
          'profile-section': 'edu-direct-deposit-information',
          'error-key': `${errorName}-save-error-api-response`,
        });
        captureDirectDepositErrorResponse({
          error: { ...err, ...response, source: ERROR_SOURCES.API },
          apiEventName: 'profile-put-edu-direct-deposit-failed',
        });

        dispatch({
          type: EDU_PAYMENT_INFORMATION_SAVE_FAILED,
          response,
        });
      } else {
        recordEvent({
          event: 'profile-transaction',
          'profile-section': 'edu-direct-deposit-information',
        });
        dispatch({
          type: EDU_PAYMENT_INFORMATION_SAVE_SUCCEEDED,
          response: {
            paymentAccount: response,
          },
        });
      }
    } catch (error) {
      captureDirectDepositErrorResponse({
        error,
        apiEventName: 'profile-put-edu-direct-deposit-failed',
      });
      recordEvent({
        event: 'profile-edit-failure',
        'profile-action': 'save-failure',
        'profile-section': 'edu-direct-deposit-information',
        'error-key': 'unknown-caught-save-error',
      });
    }
  };
}

export function editEDUPaymentInformationToggled(open) {
  return { type: EDU_PAYMENT_INFORMATION_EDIT_TOGGLED, open };
}
<commit_msg>(feature) more detailed error logging (#21556)<commit_after>import recordAnalyticsEvent from 'platform/monitoring/record-event';
import {
  createCNPDirectDepositAnalyticsDataObject,
  getData,
  isEligibleForCNPDirectDeposit,
  isSignedUpForCNPDirectDeposit,
  isSignedUpForEDUDirectDeposit,
} from '../util';

import { captureError, ERROR_SOURCES } from '../util/analytics';

export const CNP_PAYMENT_INFORMATION_FETCH_STARTED =
  'CNP_PAYMENT_INFORMATION_FETCH_STARTED';
export const CNP_PAYMENT_INFORMATION_FETCH_SUCCEEDED =
  'CNP_PAYMENT_INFORMATION_FETCH_SUCCEEDED';
export const CNP_PAYMENT_INFORMATION_FETCH_FAILED =
  'CNP_PAYMENT_INFORMATION_FETCH_FAILED';

export const CNP_PAYMENT_INFORMATION_EDIT_TOGGLED =
  'CNP_PAYMENT_INFORMATION_EDIT_TOGGLED';

export const CNP_PAYMENT_INFORMATION_SAVE_STARTED =
  'CNP_PAYMENT_INFORMATION_SAVE_STARTED';
export const CNP_PAYMENT_INFORMATION_SAVE_SUCCEEDED =
  'CNP_PAYMENT_INFORMATION_SAVE_SUCCEEDED';
export const CNP_PAYMENT_INFORMATION_SAVE_FAILED =
  'CNP_PAYMENT_INFORMATION_SAVE_FAILED';

export const EDU_PAYMENT_INFORMATION_FETCH_STARTED =
  'EDU_PAYMENT_INFORMATION_FETCH_STARTED';
export const EDU_PAYMENT_INFORMATION_FETCH_SUCCEEDED =
  'EDU_PAYMENT_INFORMATION_FETCH_SUCCEEDED';
export const EDU_PAYMENT_INFORMATION_FETCH_FAILED =
  'EDU_PAYMENT_INFORMATION_FETCH_FAILED';

export const EDU_PAYMENT_INFORMATION_EDIT_TOGGLED =
  'EDU_PAYMENT_INFORMATION_EDIT_TOGGLED';

export const EDU_PAYMENT_INFORMATION_SAVE_STARTED =
  'EDU_PAYMENT_INFORMATION_SAVE_STARTED';
export const EDU_PAYMENT_INFORMATION_SAVE_SUCCEEDED =
  'EDU_PAYMENT_INFORMATION_SAVE_SUCCEEDED';
export const EDU_PAYMENT_INFORMATION_SAVE_FAILED =
  'EDU_PAYMENT_INFORMATION_SAVE_FAILED';

const captureDirectDepositErrorResponse = ({ error, apiEventName }) => {
  const [firstError = {}] = error.errors ?? [];
  const {
    code = 'code-unknown',
    title = 'title-unknown',
    detail = 'detail-unknown',
    status = 'status-unknown',
  } = firstError;

  captureError(error, {
    eventName: apiEventName,
    code,
    title,
    detail,
    status,
  });
};

export function fetchCNPPaymentInformation(recordEvent = recordAnalyticsEvent) {
  return async dispatch => {
    dispatch({ type: CNP_PAYMENT_INFORMATION_FETCH_STARTED });

    recordEvent({ event: `profile-get-cnp-direct-deposit-started` });
    const response = await getData('/ppiu/payment_information');

    // sample error when getting payment information
    // response = {
    //   errors: [
    //     {
    //       title: 'Bad Gateway',
    //       detail: 'Received an an invalid response from the upstream server',
    //       code: 'EVSS502',
    //       source: 'EVSS::PPIU::Service',
    //       status: '502',
    //     },
    //   ],
    // };

    if (response.error) {
      recordEvent({ event: `profile-get-cnp-direct-deposit-failed` });
      dispatch({
        type: CNP_PAYMENT_INFORMATION_FETCH_FAILED,
        response,
      });
    } else {
      recordEvent({
        event: `profile-get-cnp-direct-deposit-retrieved`,
        // The API might report an empty payment address for some folks who are
        // already enrolled in direct deposit. But we want to make sure we
        // always treat those who are signed up as being eligible. Therefore
        // we'll check to see if they either have a payment address _or_ are
        // already signed up for direct deposit here:
        'direct-deposit-setup-eligible':
          isEligibleForCNPDirectDeposit(response) ||
          isSignedUpForCNPDirectDeposit(response),
        'direct-deposit-setup-complete': isSignedUpForCNPDirectDeposit(
          response,
        ),
      });
      dispatch({
        type: CNP_PAYMENT_INFORMATION_FETCH_SUCCEEDED,
        response,
      });
    }
  };
}

export function saveCNPPaymentInformation(
  fields,
  isEnrollingInDirectDeposit = false,
  recordEvent = recordAnalyticsEvent,
) {
  return async dispatch => {
    let gaClientId;
    try {
      // eslint-disable-next-line no-undef
      gaClientId = ga.getAll()[0].get('clientId');
    } catch (e) {
      // don't want to break submitting because of a weird GA issue
    }
    const apiRequestOptions = {
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...fields,
        gaClientId,
      }),
      method: 'PUT',
      mode: 'cors',
    };

    dispatch({ type: CNP_PAYMENT_INFORMATION_SAVE_STARTED });

    const response = await getData(
      '/ppiu/payment_information',
      apiRequestOptions,
    );

    // Leaving this here for the time being while we wrap up the error handling
    // const response = {
    //   error: {
    //     errors: [
    //       {
    //         title: 'Unprocessable Entity',
    //         detail: 'One or more unprocessable user payment properties',
    //         code: '126',
    //         source: 'EVSS::PPIU::Service',
    //         status: '422',
    //         meta: {
    //           messages: [
    //             {
    //               key: 'cnp.payment.generic.error.message',
    //               severity: 'ERROR',
    //               text:
    //                 'Generic CnP payment update error. Update response: Update Failed: Night area number is invalid, must be 3 digits',
    //             },
    //           ],
    //         },
    //       },
    //     ],
    //   },
    // };

    if (response.error || response.errors) {
      const errors = response.error?.errors || [];
      const analyticsData = createCNPDirectDepositAnalyticsDataObject(
        errors,
        isEnrollingInDirectDeposit,
      );
      recordEvent(analyticsData);
      dispatch({
        type: CNP_PAYMENT_INFORMATION_SAVE_FAILED,
        response,
      });
    } else {
      recordEvent({
        event: 'profile-transaction',
        'profile-section': `cnp-direct-deposit-information`,
      });
      dispatch({
        type: CNP_PAYMENT_INFORMATION_SAVE_SUCCEEDED,
        response,
      });
    }
  };
}

export function editCNPPaymentInformationToggled(open) {
  return { type: CNP_PAYMENT_INFORMATION_EDIT_TOGGLED, open };
}

export function fetchEDUPaymentInformation(recordEvent = recordAnalyticsEvent) {
  return async dispatch => {
    dispatch({ type: EDU_PAYMENT_INFORMATION_FETCH_STARTED });

    recordEvent({ event: 'profile-get-edu-direct-deposit-started' });
    try {
      const response = await getData('/profile/ch33_bank_accounts');
      // .errors is returned from the API, .error is returned from getData
      if (response.errors || response.error) {
        const err = response.error || response.errors;
        recordEvent({ event: 'profile-get-edu-direct-deposit-failed' });
        captureDirectDepositErrorResponse({
          error: { ...err, source: ERROR_SOURCES.API },
          apiEventName: 'profile-get-edu-direct-deposit-failed',
        });
        dispatch({
          type: EDU_PAYMENT_INFORMATION_FETCH_FAILED,
          response,
        });
      } else {
        recordEvent({
          event: 'profile-get-edu-direct-deposit-retrieved',
          // NOTE: the GET profile/ch33_bank_accounts/ is not able to tell us if a
          // user is eligible to set up DD for EDU, so we are only reporting if
          // they are currently enrolled in DD for EDU or not
          'direct-deposit-setup-complete': isSignedUpForEDUDirectDeposit(
            response,
          ),
        });
        dispatch({
          type: EDU_PAYMENT_INFORMATION_FETCH_SUCCEEDED,
          response: {
            paymentAccount: response,
          },
        });
      }
    } catch (error) {
      recordEvent({ event: 'profile-get-edu-direct-deposit-failed' });
      captureDirectDepositErrorResponse({
        error,
        apiEventName: 'profile-get-edu-direct-deposit-failed',
      });
      dispatch({
        type: EDU_PAYMENT_INFORMATION_FETCH_FAILED,
        response: { error },
      });
    }
  };
}

export function saveEDUPaymentInformation(
  fields,
  recordEvent = recordAnalyticsEvent,
) {
  return async dispatch => {
    let gaClientId;
    try {
      // eslint-disable-next-line no-undef
      gaClientId = ga.getAll()[0].get('clientId');
    } catch (e) {
      // don't want to break submitting because of a weird GA issue
    }
    const apiRequestOptions = {
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...fields,
        gaClientId,
      }),
      method: 'PUT',
      mode: 'cors',
    };

    dispatch({ type: EDU_PAYMENT_INFORMATION_SAVE_STARTED });
    try {
      const response = await getData(
        '/profile/ch33_bank_accounts',
        apiRequestOptions,
      );
      // .errors is returned from the API, .error is returned from getData
      if (response.errors || response.error) {
        const err = response.errors || response.error?.errors;
        let errorName = 'unknown';
        if (err) {
          if (err.length > 0) {
            errorName = err[0].title;
          } else {
            errorName = err?.title || 'unknown-title';
          }
        }
        recordEvent({
          event: 'profile-edit-failure',
          'profile-action': 'save-failure',
          'profile-section': 'edu-direct-deposit-information',
          'error-key': `${errorName}-save-error-api-response`,
        });
        captureDirectDepositErrorResponse({
          error: { err, response, source: ERROR_SOURCES.API },
          apiEventName: 'profile-put-edu-direct-deposit-api-failed',
        });

        dispatch({
          type: EDU_PAYMENT_INFORMATION_SAVE_FAILED,
          response,
        });
      } else {
        recordEvent({
          event: 'profile-transaction',
          'profile-section': 'edu-direct-deposit-information',
        });
        dispatch({
          type: EDU_PAYMENT_INFORMATION_SAVE_SUCCEEDED,
          response: {
            paymentAccount: response,
          },
        });
      }
    } catch (error) {
      captureDirectDepositErrorResponse({
        error,
        apiEventName: 'profile-put-edu-direct-deposit-failed-exception',
      });
      recordEvent({
        event: 'profile-edit-failure',
        'profile-action': 'save-failure',
        'profile-section': 'edu-direct-deposit-information',
        'error-key': 'unknown-caught-save-error',
      });
    }
  };
}

export function editEDUPaymentInformationToggled(open) {
  return { type: EDU_PAYMENT_INFORMATION_EDIT_TOGGLED, open };
}
<|endoftext|>


<commit_before>"use strict";
var _gen = require('./gen.js'),
    genNoDup = _gen.genNoDup;
var _parse = require('./parse.js'),
    unparse = _parse.unparse;

const varCount = 3;
const depth = 6;
const name = `gen_${varCount}_${depth}`;
var vars = [];
for (var i = 0; i < varCount; i++) { vars.push("a" + i); }

console.log(`var ${name}  = [`);

for (let exp of genNoDup(vars, 6)) {
    const ue = unparse(exp)
    if (ue == "((λ_0 (_0 _0)) (λ_1 (_1 _1)))") continue;
    if (ue == "(λ_0 ((λ_1 (_1 _1)) (λ_2 (_2 _2))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 (λ_2 (_1 _1))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 (λ_2 (_1 _1))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 (_1 (_1 _1))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 ((_1 _1) _1)))") continue;
    if (ue == "((λ_0 (λ_1 (_0 _0))) (λ_2 (_2 _2)))") continue;
    if (ue == "((λ_0 (_0 (_0 _0))) (λ_1 (_1 _1)))") continue;
    if (ue == "((λ_0 ((_0 _0) _0)) (λ_1 (_1 _1)))") continue;
    // needs rename:  ((λ_0 (_0 _0)) (λ_1 (λ_2 (_1 _2))))
    console.log("    "  + JSON.stringify(exp) + ",");
}
console.log("];");
console.log(`exports.${name} = ${name};`);
<commit_msg>change variable name to generated_m_n<commit_after>"use strict";
var _gen = require('./gen.js'),
    genNoDup = _gen.genNoDup;
var _parse = require('./parse.js'),
    unparse = _parse.unparse;

const varCount = 3;
const depth = 6;
const name = `generated_${varCount}_${depth}`;
var vars = [];
for (var i = 0; i < varCount; i++) { vars.push("a" + i); }

console.log(`var ${name}  = [`);

for (let exp of genNoDup(vars, 6)) {
    const ue = unparse(exp)
    if (ue == "((λ_0 (_0 _0)) (λ_1 (_1 _1)))") continue;
    if (ue == "(λ_0 ((λ_1 (_1 _1)) (λ_2 (_2 _2))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 (λ_2 (_1 _1))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 (λ_2 (_1 _1))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 (_1 (_1 _1))))") continue;
    if (ue == "((λ_0 (_0 _0)) (λ_1 ((_1 _1) _1)))") continue;
    if (ue == "((λ_0 (λ_1 (_0 _0))) (λ_2 (_2 _2)))") continue;
    if (ue == "((λ_0 (_0 (_0 _0))) (λ_1 (_1 _1)))") continue;
    if (ue == "((λ_0 ((_0 _0) _0)) (λ_1 (_1 _1)))") continue;
    // needs rename:  ((λ_0 (_0 _0)) (λ_1 (λ_2 (_1 _2))))
    console.log("    "  + JSON.stringify(exp) + ",");
}
console.log("];");
console.log(`exports.${name} = ${name};`);
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import classNames from 'classnames';
import { fabric } from 'fabric';

import { brushSettings, fontsSettings, canvasSettings } from '../drawings.config';
import PTButton from '../../../ui-elements/PTButton/PTButton';

export default class DrawingsPaint extends PureComponent {
  state = {
    currentView: 'brush',
    brushSize: brushSettings.sizes[0],
    brushColor: brushSettings.colors.base[0],
    textParams: {
      fill: fontsSettings.fills[0],
      text: '',
      fontSize: fontsSettings.fontSizes[2],
      fontFamily: fontsSettings.fontFamilies[0],
    },
    addTextObject: null,
    canvasId: 'drawing-canvas',
    canvas: null,
    canvasEl: null,
    uploadParams: null,
    uploadImageURL: '',
    isCanDelete: false,
    imagesName: '',
    file: '',
  };

  componentWillMount() {
    const { detail } = this.props;
    if (detail) {
      this.showEditCanvas(detail);
    }
    this.clearTextParams();
  }

  componentDidMount() {
    this.initCanvasDrawing();
  }

  setCanvasSize = () => {
    const { canvasEl, canvas } = this.state;
    const ratio = 0.5625; // 9/16
    const holder = canvasEl.closest('.drawing-canvas-holder');
    const width = holder.offsetWidth - 2;

    if (holder) {
      canvas.setWidth(width);
      canvas.setHeight(width * ratio);
    }
  };

  setModeBrush = () => {
    const { canvas, brushSize, brushColor } = this.state;
    canvas.isDrawingMode = true;
    canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
    canvas.freeDrawingBrush.color = brushColor;
    canvas.freeDrawingBrush.width = brushSize;
  };

  getCanvasImage64 = () => {
    const { onChangeImageCanvas } = this.props;
    let image64 = null;
    if (!fabric.Canvas.supports('toDataURL')) {
      console.log('This browser doesn\'t provide means to serialize canvas to an image');
    } else {
      image64 = this.state.canvas.toDataURL('image/jpeg', 1.0);
    }
    onChangeImageCanvas(image64);
  };

  initCanvasDrawing = () => {
    const { canvasId } = this.state;
    this.setState({ canvasEl: document.getElementById(canvasId), canvas: new fabric.Canvas(canvasId, canvasSettings) });
    setTimeout(() => {
      this.subscribeToCanvasEvents();
      this.setCanvasSize();
      this.setModeBrush();
    }, 0);
  };

  canvasModified = () => {
    this.setIsCanDelete();
    this.getCanvasImage64();
  };

  subscribeToCanvasEvents = () => {
    this.state.canvas.on('object:selected', (object) => {
      this.setTextObject(object.target);
      this.setIsCanDelete();
    });
    this.state.canvas.on('object:modified', this.canvasModified);
    this.state.canvas.on('object:added', this.canvasModified);
    this.state.canvas.on('object:removed', this.canvasModified);
    this.state.canvas.on('mouse:up', this.canvasModified);
  };

  setModeFont = () => {
    this.state.canvas.isDrawingMode = false;
  };

   setModePicture = () => {
     this.state.canvas.isDrawingMode = false;
   };

  setModeDrawing = (mode) => {
    switch (mode) {
      case 'fonts':
        this.setModeFont();
        break;
      case 'picture':
        this.setModePicture();
        break;
      default:
        this.setModeBrush();
    }
  };

  isActiveView = (activeView) => {
    const { currentView } = this.state;
    return activeView === currentView;
  };

  changeActiveView = (activeView) => {
    this.setState({ currentView: activeView });
    this.setModeDrawing(activeView);
  };

  changeBrushSize = (e) => {
    this.setState({ brushSize: e.target.value });
    this.state.canvas.freeDrawingBrush.width = e.target.value;
  };

  isActiveColour = (color) => {
    return color === this.state.brushColor;
  };

  changeBrushColor = (color) => {
    this.setState({ brushColor: color });
    this.state.canvas.freeDrawingBrush.color = color;
  };

  changeFontParams = (textParams) => {
    if (this.isTextObject && this.state.addTextObject) {
      this.state.addTextObject.setText(textParams.text);
      this.state.addTextObject.setFill(textParams.fill);
      this.state.addTextObject.setFontSize(textParams.fontSize);
      this.state.addTextObject.setFontFamily(textParams.fontFamily);
      this.state.canvas.renderAll();
    }
    this.setState({ textParams: {
      fill: textParams.fill,
      text: textParams.text,
      fontSize: textParams.fontSize,
      fontFamily: textParams.fontFamily,
    } });
  };

  isTextObject = () => {
    const { addTextObject } = this.state;
    return !!addTextObject;
  };

  clearTextParams = () => {
    const textParams = {
      text: 'New text',
      fill: fontsSettings.fills[0],
      fontSize: fontsSettings.fontSizes[2],
      fontWeight: 'normal',
      fontFamily: fontsSettings.fontFamilies[0],
    };
    this.setState({ textParams })
  };

  onChangeTextParams = (key, value) => {
    this.state.textParams[key] = value;
    this.changeFontParams(this.state.textParams)
  };

  setTextObject = (object) => {
    if (object instanceof fabric.Text) {
      this.state.addTextObject = object;
      this.setState({ textParams: {
        fill: object.fill,
        text: object.text,
        fontSize: object.fontSize,
        fontFamily: object.fontFamily,
      } })
    } else {
      this.state.addTextObject = null;
      this.clearTextParams();
    }
  };

  addNewText = (text) => {
    this.state.addTextObject = new fabric.Text(text, this.state.textParams);
    this.state.canvas.add(this.state.addTextObject);
  };

  calckScale = (parentWidth, parentHeight, childWidth, childHeight) => {
    let scale = 1;
    const parentRatio = parentHeight / parentWidth; // 9 / 16 (56.25%)
    const childRatio = childHeight / childWidth;

    if (childWidth > parentWidth || childHeight > parentHeight) {
      if (childRatio > parentRatio) {
        scale = parentHeight / childHeight;
      } else {
        scale = parentWidth / childWidth;
      }
    }

    return scale;
  };

  addPictureToCanvas = (base64) => {
    this.resizeCanvas();
    setTimeout(() => {
      fabric.util.loadImage(base64, (img) => {
        const object = new fabric.Image(img);
        const scale = this.calckScale(this.state.canvas.width, this.state.canvas.height, object.width, object.height);

        object.hasRotatingPoint = true;
        object.scaleX = object.scaleY = scale;
        object.isDrawingMode = true;
        this.state.canvas.add(object);
        this.state.canvas.renderAll();
      }, null, { crossOrigin: 'Anonymous' });
    }, 11);
  };

  setIsCanDelete = () => {
    if (this.state.canvas &&
      !this.state.canvas.isDrawingMode &&
      (this.state.canvas.getActiveObject() || this.state.canvas.getActiveGroup())) {
      this.setState({ isCanDelete: true });
    } else {
      this.setState({ isCanDelete: false });
    }
  };

  deleteObject = () => {
    const selectedObject = this.state.canvas.getActiveObject();
    const selectedMultipleObjects = this.state.canvas.getActiveGroup();

    if (selectedObject) {
      selectedObject.remove();
    } else {
      selectedMultipleObjects._objects.forEach((object) => {
        object.remove();
        selectedMultipleObjects.removeWithUpdate(object);
      });
    }
    setTimeout(() => {
      this.state.canvas.discardActiveGroup();
      this.state.canvas.renderAll();
    }, 10)
  };

  clearCanvas = () => {
    if (this.state.canvas) {
      this.state.canvas.clear();
    }
  };

  showEditCanvas = (data) => {
    this.clearCanvas();
    if (data.drawingBase64) {
      this.addPictureToCanvas(data.drawingBase64);
    }
  };

  resizeCanvas = () => {
    setTimeout(() => {
      this.setCanvasSize();
    }, 10);
  };

  onChange = (event) => {
    this.setState({ file: event.target.files[0] });
    this.setState({ imagesName: event.target.files[0].name });
  };

  uploadPicture = () => {
    const { file } = this.state;
    let reader;
    let imgData = {};
    if (file) {
      imgData = file;
      reader = new FileReader();

      reader.onload = (event) => {
        const image64 = event.target.result;
        imgData.imgencode = image64;
        this.addPictureToCanvas(image64);
      };

      // when the file is read it triggers the onload event above.
      reader.readAsDataURL(imgData);
    }
  };

  render() {
    const { brushSize, textParams, isCanDelete, imagesName } = this.state;
    return (

                { isCanDelete ? <PTButton className="btn btn-icon-normal btn-drawing btn-drawing-delete" onClick={() => this.deleteObject()}>

                </PTButton> : null}

                <PTButton className={classNames('btn btn-icon-normal btn-drawing', { 'active': this.isActiveView('brush') })} onClick={() => this.changeActiveView('brush')}>

                </PTButton>
                <PTButton className={classNames('btn btn-icon-normal btn-drawing', { 'active': this.isActiveView('fonts') })} onClick={() => this.changeActiveView('fonts')}>

                </PTButton>
                <PTButton className={classNames('btn btn-icon-normal btn-drawing', { 'active': this.isActiveView('picture') })} onClick={() => this.changeActiveView('picture')}>

                </PTButton>

            { this.isActiveView('brush') ?

Size:

                    <select
                      className="drawing-form-small form-control input-sm"
                      id="brushSizes"
                      name="brushSizes"
                      onChange={this.changeBrushSize}
                      value={brushSize}
                    >
                      { brushSettings.sizes.map(size => <option value={size}>{`${size}px`}</option>)}
                    </select>

Colour:

                      {Object.keys(brushSettings.colors).map((pointColors, i) => (

                          { brushSettings.colors[pointColors].map(color =>
 this.changeBrushColor(color)} style={{ background: color }} />)}

)
                      )}

 : null}
            { this.isActiveView('fonts') ?

Font:

                    <select
                      className="drawing-form-middle form-control input-sm"
                      id="fontFamily"
                      name="fontFamily"
                      value={textParams.fontFamily}
                      onChange={e => this.onChangeTextParams('fontFamily', e.target.value)}
                    >
                      { fontsSettings.fontFamilies.map(fontFamily => <option value={fontFamily}>{fontFamily}</option>)}
                    </select>

Size:

                    <select
                      className="drawing-form-small form-control input-sm"
                      id="fontSize"
                      name="fontSize"
                      value={textParams.fontSize}
                      onChange={e => this.onChangeTextParams('fontSize', e.target.value)}
                    >
                      { fontsSettings.fontSizes.map(fontSize => <option value={fontSize}>{`${fontSize}pt`}</option>)}
                    </select>

Colour:

                    <select
                      className="drawing-form-small form-control input-sm"
                      id="fontFill"
                      name="fontFill"
                      value={textParams.fill}
                      onChange={e => this.onChangeTextParams('fill', e.target.value)}
                    >
                      { fontsSettings.fills.map(fill => <option value={fill}>{fill}</option>)}
                    </select>

Text:

                    <PTButton className="btn btn-success btn-drawing-add" onClick={() => this.addNewText(addText)}>Add</PTButton>

                      <input
                        className="form-control input-sm input-with-btn"
                        type="text"
                        name="addText"
                        id="addText"
                        value={textParams.text}
                        defaultValue="New Text"
                        onChange={e => this.onChangeTextParams('text', e.target.value)}
                      />

 : null}
            { this.isActiveView('picture') ?

Select Image:

                    <PTButton className="btn btn-success btn-drawing-upload-file" onClick={() => this.uploadPicture()}>Upload</PTButton>

                        <input
                          accept="image/jpeg,image/png,image/gif"
                          type="file"
                          name="logoPath"
                          id="logoPath"
                          onChange={this.onChange}
                        />
                        Browse...

{imagesName}

 : null}

            <canvas id="drawing-canvas" className="drawing-canvas" width={500} height={406} />

    )
  }
}
<commit_msg>change in code <DrawingsPaint /> #83<commit_after>import React, { PureComponent } from 'react';
import classNames from 'classnames';
import { fabric } from 'fabric';

import { brushSettings, fontsSettings, canvasSettings } from '../drawings.config';
import PTButton from '../../../ui-elements/PTButton/PTButton';

export default class DrawingsPaint extends PureComponent {
  state = {
    currentView: 'brush',
    brushSize: brushSettings.sizes[0],
    brushColor: brushSettings.colors.base[0],
    textParams: {
      fill: fontsSettings.fills[0],
      text: '',
      fontSize: fontsSettings.fontSizes[2],
      fontFamily: fontsSettings.fontFamilies[0],
    },
    addTextObject: null,
    canvasId: 'drawing-canvas',
    canvas: null,
    canvasEl: null,
    uploadParams: null,
    uploadImageURL: '',
    isCanDelete: false,
    imagesName: '',
    file: '',
  };

  componentWillMount() {
    const { detail } = this.props;
    if (detail) {
      this.showEditCanvas(detail);
    }
    this.clearTextParams();
  }

  componentDidMount() {
    this.initCanvasDrawing();
  }

  setCanvasSize = () => {
    const { canvasEl, canvas } = this.state;
    const ratio = 0.5625; // 9/16
    const holder = canvasEl.closest('.drawing-canvas-holder');
    const width = holder.offsetWidth - 2;

    if (holder) {
      canvas.setWidth(width);
      canvas.setHeight(width * ratio);
    }
  };

  setModeBrush = () => {
    const { canvas, brushSize, brushColor } = this.state;
    canvas.isDrawingMode = true;
    canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
    canvas.freeDrawingBrush.color = brushColor;
    canvas.freeDrawingBrush.width = brushSize;
  };

  getCanvasImage64 = () => {
    const { onChangeImageCanvas } = this.props;
    let image64 = null;
    if (!fabric.Canvas.supports('toDataURL')) {
      console.log('This browser doesn\'t provide means to serialize canvas to an image');
    } else {
      image64 = this.state.canvas.toDataURL({ format: 'jpeg', quality: 0.1 });
    }
    onChangeImageCanvas(image64);
  };

  initCanvasDrawing = () => {
    const { canvasId } = this.state;
    const canvas = window._canvas = new fabric.Canvas(canvasId, canvasSettings);
    this.setState({ canvasEl: document.getElementById(canvasId), canvas });
    setTimeout(() => {
      this.subscribeToCanvasEvents();
      this.setCanvasSize();
      this.setModeBrush();
    }, 0);
  };

  canvasModified = () => {
    this.setIsCanDelete();
    this.getCanvasImage64();
  };

  subscribeToCanvasEvents = () => {
    this.state.canvas.on('object:selected', (object) => {
      this.setTextObject(object.target);
      this.setIsCanDelete();
    });
    this.state.canvas.on('object:modified', this.canvasModified);
    this.state.canvas.on('object:added', this.canvasModified);
    this.state.canvas.on('object:removed', this.canvasModified);
    this.state.canvas.on('mouse:up', this.canvasModified);
  };

  setModeFont = () => {
    this.state.canvas.isDrawingMode = false;
  };

   setModePicture = () => {
     this.state.canvas.isDrawingMode = false;
   };

  setModeDrawing = (mode) => {
    switch (mode) {
      case 'fonts':
        this.setModeFont();
        break;
      case 'picture':
        this.setModePicture();
        break;
      default:
        this.setModeBrush();
    }
  };

  isActiveView = (activeView) => {
    const { currentView } = this.state;
    return activeView === currentView;
  };

  changeActiveView = (activeView) => {
    this.setState({ currentView: activeView });
    this.setModeDrawing(activeView);
  };

  changeBrushSize = (e) => {
    this.setState({ brushSize: e.target.value });
    this.state.canvas.freeDrawingBrush.width = e.target.value;
  };

  isActiveColour = (color) => {
    return color === this.state.brushColor;
  };

  changeBrushColor = (color) => {
    this.setState({ brushColor: color });
    this.state.canvas.freeDrawingBrush.color = color;
  };

  changeFontParams = (textParams) => {
    if (this.isTextObject && this.state.addTextObject) {
      this.state.addTextObject.setText(textParams.text);
      this.state.addTextObject.setFill(textParams.fill);
      this.state.addTextObject.setFontSize(textParams.fontSize);
      this.state.addTextObject.setFontFamily(textParams.fontFamily);
      this.state.canvas.renderAll();
    }
    this.setState({ textParams: {
      fill: textParams.fill,
      text: textParams.text,
      fontSize: textParams.fontSize,
      fontFamily: textParams.fontFamily,
    } });
  };

  isTextObject = () => {
    const { addTextObject } = this.state;
    return !!addTextObject;
  };

  clearTextParams = () => {
    const textParams = {
      text: 'New text',
      fill: fontsSettings.fills[0],
      fontSize: fontsSettings.fontSizes[2],
      fontWeight: 'normal',
      fontFamily: fontsSettings.fontFamilies[0],
    };
    this.setState({ textParams })
  };

  onChangeTextParams = (key, value) => {
    this.state.textParams[key] = value;
    this.changeFontParams(this.state.textParams)
  };

  setTextObject = (object) => {
    if (object instanceof fabric.Text) {
      this.state.addTextObject = object;
      this.setState({ textParams: {
        fill: object.fill,
        text: object.text,
        fontSize: object.fontSize,
        fontFamily: object.fontFamily,
      } })
    } else {
      this.state.addTextObject = null;
      this.clearTextParams();
    }
  };

  addNewText = (text) => {
    this.state.addTextObject = new fabric.Text(text, this.state.textParams);
    this.state.canvas.add(this.state.addTextObject);
  };

  calckScale = (parentWidth, parentHeight, childWidth, childHeight) => {
    let scale = 1;
    const parentRatio = parentHeight / parentWidth; // 9 / 16 (56.25%)
    const childRatio = childHeight / childWidth;

    if (childWidth > parentWidth || childHeight > parentHeight) {
      if (childRatio > parentRatio) {
        scale = parentHeight / childHeight;
      } else {
        scale = parentWidth / childWidth;
      }
    }

    return scale;
  };

  addPictureToCanvas = (base64) => {
    this.resizeCanvas();
    setTimeout(() => {
      fabric.util.loadImage(base64, (img) => {
        const object = new fabric.Image(img);
        const scale = this.calckScale(this.state.canvas.width, this.state.canvas.height, object.width, object.height);

        object.hasRotatingPoint = true;
        object.scaleX = object.scaleY = scale;
        object.isDrawingMode = true;
        this.state.canvas.add(object);
        this.state.canvas.renderAll();
      }, null, { crossOrigin: 'Anonymous' });
    }, 11);
  };

  setIsCanDelete = () => {
    if (this.state.canvas &&
      !this.state.canvas.isDrawingMode &&
      (this.state.canvas.getActiveObject() || this.state.canvas.getActiveGroup())) {
      this.setState({ isCanDelete: true });
    } else {
      this.setState({ isCanDelete: false });
    }
  };

  deleteObject = () => {
    const selectedObject = this.state.canvas.getActiveObject();
    const selectedMultipleObjects = this.state.canvas.getActiveGroup();

    if (selectedObject) {
      selectedObject.remove();
    } else {
      selectedMultipleObjects._objects.forEach((object) => {
        object.remove();
        selectedMultipleObjects.removeWithUpdate(object);
      });
    }
    setTimeout(() => {
      this.state.canvas.discardActiveGroup();
      this.state.canvas.renderAll();
    }, 10)
  };

  clearCanvas = () => {
    if (this.state.canvas) {
      this.state.canvas.clear();
    }
  };

  showEditCanvas = (data) => {
    this.clearCanvas();
    if (data.drawingBase64) {
      this.addPictureToCanvas(data.drawingBase64);
    }
  };

  resizeCanvas = () => {
    setTimeout(() => {
      this.setCanvasSize();
    }, 10);
  };

  onChange = (event) => {
    this.setState({ file: event.target.files[0] });
    this.setState({ imagesName: event.target.files[0].name });
  };

  uploadPicture = () => {
    const { file } = this.state;
    let reader;
    let imgData = {};
    if (file) {
      imgData = file;
      reader = new FileReader();

      reader.onload = (event) => {
        const image64 = event.target.result;
        imgData.imgencode = image64;
        this.addPictureToCanvas(image64);
      };

      // when the file is read it triggers the onload event above.
      reader.readAsDataURL(imgData);
    }
  };

  render() {
    const { brushSize, textParams, isCanDelete, imagesName } = this.state;
    return (

                { isCanDelete ? <PTButton className="btn btn-icon-normal btn-drawing btn-drawing-delete" onClick={() => this.deleteObject()}>

                </PTButton> : null}

                <PTButton className={classNames('btn btn-icon-normal btn-drawing', { 'active': this.isActiveView('brush') })} onClick={() => this.changeActiveView('brush')}>

                </PTButton>
                <PTButton className={classNames('btn btn-icon-normal btn-drawing', { 'active': this.isActiveView('fonts') })} onClick={() => this.changeActiveView('fonts')}>

                </PTButton>
                <PTButton className={classNames('btn btn-icon-normal btn-drawing', { 'active': this.isActiveView('picture') })} onClick={() => this.changeActiveView('picture')}>

                </PTButton>

            { this.isActiveView('brush') ?

Size:

                    <select
                      className="drawing-form-small form-control input-sm"
                      id="brushSizes"
                      name="brushSizes"
                      onChange={this.changeBrushSize}
                      value={brushSize}
                    >
                      { brushSettings.sizes.map(size => <option value={size}>{`${size}px`}</option>)}
                    </select>

Colour:

                      {Object.keys(brushSettings.colors).map((pointColors, i) => (

                          { brushSettings.colors[pointColors].map(color =>
 this.changeBrushColor(color)} style={{ background: color }} />)}

)
                      )}

 : null}
            { this.isActiveView('fonts') ?

Font:

                    <select
                      className="drawing-form-middle form-control input-sm"
                      id="fontFamily"
                      name="fontFamily"
                      value={textParams.fontFamily}
                      onChange={e => this.onChangeTextParams('fontFamily', e.target.value)}
                    >
                      { fontsSettings.fontFamilies.map(fontFamily => <option value={fontFamily}>{fontFamily}</option>)}
                    </select>

Size:

                    <select
                      className="drawing-form-small form-control input-sm"
                      id="fontSize"
                      name="fontSize"
                      value={textParams.fontSize}
                      onChange={e => this.onChangeTextParams('fontSize', e.target.value)}
                    >
                      { fontsSettings.fontSizes.map(fontSize => <option value={fontSize}>{`${fontSize}pt`}</option>)}
                    </select>

Colour:

                    <select
                      className="drawing-form-small form-control input-sm"
                      id="fontFill"
                      name="fontFill"
                      value={textParams.fill}
                      onChange={e => this.onChangeTextParams('fill', e.target.value)}
                    >
                      { fontsSettings.fills.map(fill => <option value={fill}>{fill}</option>)}
                    </select>

Text:

                    <PTButton className="btn btn-success btn-drawing-add" onClick={() => this.addNewText(addText)}>Add</PTButton>

                      <input
                        className="form-control input-sm input-with-btn"
                        type="text"
                        name="addText"
                        id="addText"
                        value={textParams.text}
                        defaultValue="New Text"
                        onChange={e => this.onChangeTextParams('text', e.target.value)}
                      />

 : null}
            { this.isActiveView('picture') ?

Select Image:

                    <PTButton className="btn btn-success btn-drawing-upload-file" onClick={() => this.uploadPicture()}>Upload</PTButton>

                        <input
                          accept="image/jpeg,image/png,image/gif"
                          type="file"
                          name="logoPath"
                          id="logoPath"
                          onChange={this.onChange}
                        />
                        Browse...

{imagesName}

 : null}

            <canvas id="drawing-canvas" className="drawing-canvas" width={500} height={406} />

    )
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright 2018 Expedia Group
 *
 *       Licensed under the Apache License, Version 2.0 (the "License");
 *       you may not use this file except in compliance with the License.
 *       You may obtain a copy of the License at
 *
 *           http://www.apache.org/licenses/LICENSE-2.0
 *
 *       Unless required by applicable law or agreed to in writing, software
 *       distributed under the License is distributed on an "AS IS" BASIS,
 *       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *       See the License for the specific language governing permissions and
 *       limitations under the License.
 *
 */
import axios from 'axios';
import {observable, action} from 'mobx';
import {ErrorHandlingStore} from '../../../../stores/errorHandlingStore';

export class SearchableKeysStore extends ErrorHandlingStore  {
    @observable keys = {};

    @action fetchKeys() {
        if (Object.keys(this.keys).length > 2) { // serviceName and operationName are default
            return;                              // don't re-trigger if other keys are available
        }
        axios
            .get('/api/traces/searchableKeys')
            .then((response) => {
                this.keys = response.data;
                if (this.keys.error) {
                    this.keys.error.values = ['true', 'false'];
                }
            })
            .catch((result) => {
                SearchableKeysStore.handleError(result);
            }
        );
    }
}

export default new SearchableKeysStore();
<commit_msg>Fix fetch keys refresh condition<commit_after>/*
 * Copyright 2018 Expedia Group
 *
 *       Licensed under the Apache License, Version 2.0 (the "License");
 *       you may not use this file except in compliance with the License.
 *       You may obtain a copy of the License at
 *
 *           http://www.apache.org/licenses/LICENSE-2.0
 *
 *       Unless required by applicable law or agreed to in writing, software
 *       distributed under the License is distributed on an "AS IS" BASIS,
 *       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *       See the License for the specific language governing permissions and
 *       limitations under the License.
 *
 */
import axios from 'axios';
import {observable, action} from 'mobx';
import {ErrorHandlingStore} from '../../../../stores/errorHandlingStore';

export class SearchableKeysStore extends ErrorHandlingStore  {
    @observable keys = {};

    @action fetchKeys() {
        if (Object.keys(this.keys).length > 3) { // traceId, serviceName and operationName are default
            return;                              // don't re-trigger if other keys are available
        }
        axios
            .get('/api/traces/searchableKeys')
            .then((response) => {
                this.keys = response.data;
                if (this.keys.error) {
                    this.keys.error.values = ['true', 'false'];
                }
            })
            .catch((result) => {
                SearchableKeysStore.handleError(result);
            }
        );
    }
}

export default new SearchableKeysStore();
<|endoftext|>


<commit_before>/**
 *
 * @source: https://github.com/dnatsvlishvili/geo-key
 *
 * @licstart  The following is the entire license notice for the
 *  JavaScript code in this page.
 *
 * The MIT License (MIT)

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

(function(window, document, undefined){
  "use strict";

  var GeoKey = function(params){
    // Fix silently
    if (!(this instanceof GeoKey)) {
      return new GeoKey(arguments[0]);
    };

    // Target set to non empty value will not work on IE6
    var defaults = {
    };

    // Provides ability to provide custom parameters
    this.params = function(input, defaults) {
      if (input) {
        for (var key in input) {
          var val = input[key];
          if (typeof val !== 'undefined') {
            defaults[key] = val;
          }
        }
      }
      return defaults;
    }(params, defaults);

    // Elements that need to be worked on
    this.elements = function(params){
      if (params.target === '') {
        return document.querySelectorAll('input[type=text],input[type=edit],textarea,div,iframe');
      } else {
        return document.getElementsByClassName(params.target);
      }
    }(this.params);

    // Track changes to inputs set
    var input, that = this;
    for (var c = 0; c < this.elements.length; c += 1) {
      input = this.elements[c];
      (function(that, input) {
        that.listen(input, 'keypress', function(event){
          GeoKey.prototype.convert(input, event);
        });
      })(that, input);
    }
  };

  // Returns [a-z] input string in Georgian
  GeoKey.prototype.translate = function(string) {
    var input = string.split('');
    var result = '', offset, chr;

    var charset = {
      a: 1, b: 1, d: 1, e: 1, i: 1, g: -3, v: -15,
      z: -18, T: 21, J: 39, f: 16, q: 6, R: 38, S: 39,
      C: 56, c: 25, Z: 35, w: 7, W: 40, x: 8, j: 23, h: 26
    };

    var scope = [67, 74, 82, 83, 84, 87, 90];
    for (var n = 96; n < 122; n += 1, scope.push(n));

    for (var i = 0; i < input.length; i += 1) {
      chr = input[i];

      offset = chr.charCodeAt();
      if (typeof charset[chr] !== 'undefined') {
        offset += charset[chr];
      }

      result += (scope.indexOf(chr.charCodeAt()) > -1) ? String.fromCharCode(4206 + offset) : chr;
    }

    return result;
  };

  GeoKey.prototype.listen = function(element, eventName, callback) {
    if (element.nodeName === 'IFRAME') {
      element = element.contentWindow.document;
    }
    if (element.addEventListener) {
        element.addEventListener(eventName, callback, false);
    } else if (element.attachEvent) {
        element.attachEvent("on" + eventName, callback);
    }
  }

  // Works on a DOM element to replace a character upon keypress
  GeoKey.prototype.convert = function(element, event) {
    var start, end;
    var character = typeof event.which === 'number' ? event.which : event.keyCode;

    console.log(element);

    if (!character || character <= 32) {
      return false;
    }

    if (['INPUT','TEXTAREA'].indexOf(element.nodeName) > -1) {
      // Text convertion for <input> and <textarea> elements
      if (typeof element.selectionStart === 'number' && typeof element.selectionEnd === 'number') {
        // For modern browsers and IE9+
        start = element.selectionStart;
        end = element.selectionEnd;
        element.value = element.value.slice(0, start) + GeoKey.prototype.translate(String.fromCharCode(character)) + element.value.slice(end);
        element.selectionStart = element.selectionEnd = start + 1;
      } else {
        // For IE6, IE7, IE8
        range = document.selection.createRange();

        charLength = element.value.length;
        normal = element.value.replace(/\r\n/g, "\n");

        // Input scope only
        textInputRange = element.createTextRange();
        textInputRange.moveToBookmark(range.getBookmark());

        // Start and end of the selection should be at the end of input
        endRange = element.createTextRange();
        endRange.collapse(false);

        if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) {
            start = end = charLength;
        } else {
            start = -textInputRange.moveStart('character', -charLength);
            start += normal.slice(0, start).split("\n").length - 1;

            if (textInputRange.compareEndPoints('EndToEnd', endRange) > -1) {
                end = charLength;
            } else {
                end = -textInputRange.moveEnd('character', -charLength);
                end += normal.slice(0, end).split("\n").length - 1;
            }
        }

        // Add computed value in the right place
        element.value = element.value.slice(0, start) + GeoKey.prototype.translate(String.fromCharCode(character)) + element.value.slice(end);
        start++;

        textInputRange = element.createTextRange();
        textInputRange.collapse(true);
        textInputRange.move('character', start - (element.value.slice(0, start).split("\r\n").length - 1));
        textInputRange.select();
      }
    } else if (['DIV','IFRAME'].indexOf(element.nodeName) > -1) {

      // Text convertion for content-editable <div> elements
      var textSelection, textInputRange, textNode, context;

      var context = (element.contentWindow || element.contentDocument) || null;
      var contextWindow = (element.nodeName === 'IFRAME') ? context.window : window;
      var contextDocument = (element.nodeName === 'IFRAME') ? context.document : document;

      if (contextWindow.getSelection) {
        // For newer browsers
        textSelection = contextWindow.getSelection();
        if (textSelection.getRangeAt && textSelection.rangeCount) {
          // Get range and put a converted character in the appropriate place
          textInputRange = textSelection.getRangeAt(0);
          textInputRange.deleteContents();
          textNode = contextDocument.createTextNode(GeoKey.prototype.translate(String.fromCharCode(character)));
          textInputRange.insertNode(textNode);

          // Move point to last character
          textInputRange.setStart(textNode, textNode.length);
          textInputRange.setEnd(textNode, textNode.length);
          textSelection.removeAllRanges();
          textSelection.addRange(textInputRange);
        }
      } else if (contextDocument.selection && contextDocument.selection.createRange) {
        // For older IE browsers
        textInputRange = contextDocument.selection.createRange();
        textInputRange.pasteHTML(GeoKey.prototype.translate(String.fromCharCode(character)));
      }
    }

    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
      return event;
    }
  }

  /**
   *
   * Polyfills and shims for older browsers that might have a problem with GeoKey Library
   * @source: https://github.com/inexorabletash/polyfill
   *
   */

  // document.querySelectorAll
  if (!document.querySelectorAll) {
    document.querySelectorAll = function (selectors) {
      var style = document.createElement('style'), elements = [], element;
      document.documentElement.firstChild.appendChild(style);
      document._qsa = [];

      style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
      window.scrollBy(0, 0);
      style.parentNode.removeChild(style);

      while (document._qsa.length) {
        element = document._qsa.shift();
        element.style.removeAttribute('x-qsa');
        elements.push(element);
      }
      document._qsa = null;
      return elements;
    };
  }

  // document.getElementsByClassName
  if (typeof document.getElementsByClassName!='function') {
      document.getElementsByClassName = function() {
          var elms = document.getElementsByTagName('*');
          var ei = new Array();
          for (i=0;i<elms.length;i++) {
              if (elms[i].getAttribute('class')) {
                  ecl = elms[i].getAttribute('class').split(' ');
                  for (j=0;j<ecl.length;j++) {
                      if (ecl[j].toLowerCase() == arguments[0].toLowerCase()) {
                          ei.push(elms[i]);
                      }
                  }
              } else if (elms[i].className) {
                  ecl = elms[i].className.split(' ');
                  for (j=0;j<ecl.length;j++) {
                      if (ecl[j].toLowerCase() == arguments[0].toLowerCase()) {
                          ei.push(elms[i]);
                      }
                  }
              }
          }
          return ei;
      }
  }

  // [].map
  if (!Array.prototype.map) {
    Array.prototype.map = function (fun /*, thisp */) {
      "use strict";

      if (this === void 0 || this === null) { throw new TypeError(); }

      var t = Object(this);
      var len = t.length >>> 0;
      if (typeof fun !== "function") { throw new TypeError(); }

      var res = []; res.length = len;
      var thisp = arguments[1], i;
      for (i = 0; i < len; i++) {
        if (i in t) {
          res[i] = fun.call(thisp, t[i], i, t);
        }
      }

      return res;
    };
  }

  // [].indexOf
  if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
      "use strict";

      if (this === void 0 || this === null) { throw new TypeError(); }

      var t = Object(this);
      var len = t.length >>> 0;
      if (len === 0) { return -1; }

      var n = 0;
      if (arguments.length > 0) {
        n = Number(arguments[1]);
        if (isNaN(n)) {
          n = 0;
        } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
        }
      }

      if (n >= len) { return -1; }

      var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);

      for (; k < len; k++) {
        if (k in t && t[k] === searchElement) {
          return k;
        }
      }
      return -1;
    };
  }

  // Binds a function constructor to global object
  window.GeoKey = GeoKey;
})(window, document);<commit_msg>Fixes #2<commit_after>/**
 *
 * @source: https://github.com/dnatsvlishvili/geo-key
 *
 * @licstart  The following is the entire license notice for the
 *  JavaScript code in this page.
 *
 * The MIT License (MIT)

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

(function(window, document, undefined){
  "use strict";

  var GeoKey = function(params){
    // Fix silently
    if (!(this instanceof GeoKey)) {
      return new GeoKey(arguments[0]);
    };

    // Target set to non empty value will not work on IE6
    var defaults = {
    };

    // Provides ability to provide custom parameters
    this.params = function(input, defaults) {
      if (input) {
        for (var key in input) {
          var val = input[key];
          if (typeof val !== 'undefined') {
            defaults[key] = val;
          }
        }
      }
      return defaults;
    }(params, defaults);

    // Elements that need to be worked on
    this.elements = function(params){
      if (params.target === '') {
        return document.querySelectorAll('input[type=text],input[type=edit],textarea,div,iframe');
      } else {
        return document.getElementsByClassName(params.target);
      }
    }(this.params);

    // Track changes to inputs set
    var input, that = this;
    for (var c = 0; c < this.elements.length; c += 1) {
      input = this.elements[c];
      (function(that, input) {
        that.listen(input, 'keypress', function(event){
          GeoKey.prototype.convert(input, event);
        });
      })(that, input);
    }
  };

  // Returns [a-z] input string in Georgian
  GeoKey.prototype.translate = function(string) {
    var input = string.split('');
    var result = '', offset, chr;

    var charset = {
      a: 1, b: 1, d: 1, e: 1, i: 1, g: -3, v: -15,
      z: -18, T: 21, J: 39, f: 16, q: 6, R: 38, S: 39,
      C: 56, c: 25, Z: 35, w: 7, W: 40, x: 8, j: 23, h: 26
    };

    var scope = [67, 74, 82, 83, 84, 87, 90];
    for (var n = 96; n < 122; n += 1, scope.push(n));

    for (var i = 0; i < input.length; i += 1) {
      chr = input[i];

      offset = chr.charCodeAt();
      if (typeof charset[chr] !== 'undefined') {
        offset += charset[chr];
      }

      result += (scope.indexOf(chr.charCodeAt()) > -1) ? String.fromCharCode(4206 + offset) : chr;
    }

    return result;
  };

  GeoKey.prototype.listen = function(element, eventName, callback) {
    if (element.nodeName === 'IFRAME') {
      element = element.contentWindow.document;
    }
    if (element.addEventListener) {
        element.addEventListener(eventName, callback, false);
    } else if (element.attachEvent) {
        element.attachEvent("on" + eventName, callback);
    }
  }

  // Works on a DOM element to replace a character upon keypress
  GeoKey.prototype.convert = function(element, event) {
    var start, end;
    var character = typeof event.which === 'number' ? event.which : event.keyCode;

    console.log(element);

    if (!character || character <= 32) {
      return false;
    }

    if (['INPUT','TEXTAREA'].indexOf(element.nodeName) > -1) {
      // Text convertion for <input> and <textarea> elements
      if (typeof element.selectionStart === 'number' && typeof element.selectionEnd === 'number') {
        // For modern browsers and IE9+
        start = element.selectionStart;
        end = element.selectionEnd;
        element.value = element.value.slice(0, start) + GeoKey.prototype.translate(String.fromCharCode(character)) + element.value.slice(end);
        element.selectionStart = element.selectionEnd = start + 1;
      } else {
        // For IE6, IE7, IE8
        range = document.selection.createRange();

        charLength = element.value.length;
        normal = element.value.replace(/\r\n/g, "\n");

        // Input scope only
        textInputRange = element.createTextRange();
        textInputRange.moveToBookmark(range.getBookmark());

        // Start and end of the selection should be at the end of input
        endRange = element.createTextRange();
        endRange.collapse(false);

        if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) {
            start = end = charLength;
        } else {
            start = -textInputRange.moveStart('character', -charLength);
            start += normal.slice(0, start).split("\n").length - 1;

            if (textInputRange.compareEndPoints('EndToEnd', endRange) > -1) {
                end = charLength;
            } else {
                end = -textInputRange.moveEnd('character', -charLength);
                end += normal.slice(0, end).split("\n").length - 1;
            }
        }

        // Add computed value in the right place
        element.value = element.value.slice(0, start) + GeoKey.prototype.translate(String.fromCharCode(character)) + element.value.slice(end);
        start++;

        textInputRange = element.createTextRange();
        textInputRange.collapse(true);
        textInputRange.move('character', start - (element.value.slice(0, start).split("\r\n").length - 1));
        textInputRange.select();
      }
    } else if (['DIV','IFRAME'].indexOf(element.nodeName) > -1) {

      // Text convertion for content-editable <div> elements
      var textSelection, textInputRange, textNode, context;

      var context = (element.contentWindow || element.contentDocument) || null;
      var contextWindow = (element.nodeName === 'IFRAME') ? context.window : window;
      var contextDocument = (element.nodeName === 'IFRAME') ? context.document : document;

      if (contextWindow.getSelection) {
        // For newer browsers
        textSelection = contextWindow.getSelection();
        if (textSelection.getRangeAt && textSelection.rangeCount) {
          // Get range and put a converted character in the appropriate place
          textInputRange = textSelection.getRangeAt(0);
          textInputRange.deleteContents();
          textNode = contextDocument.createTextNode(GeoKey.prototype.translate(String.fromCharCode(character)));
          textInputRange.insertNode(textNode);

          // Move point to last character
          textInputRange.setStart(textNode, textNode.length);
          textInputRange.setEnd(textNode, textNode.length);
          textSelection.removeAllRanges();
          textSelection.addRange(textInputRange);
        }
      } else if (contextDocument.selection && contextDocument.selection.createRange) {
        // For older IE browsers
        textInputRange = contextDocument.selection.createRange();
        textInputRange.pasteHTML(GeoKey.prototype.translate(String.fromCharCode(character)));
      }
    }

    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
      return event;
    }
  }

  /**
   *
   * Polyfills and shims for older browsers that might have a problem with GeoKey Library
   * @source: https://github.com/inexorabletash/polyfill
   *
   */

  // document.querySelectorAll
  if (!document.querySelectorAll) {
    document.querySelectorAll = function (selectors) {
      var style = document.createElement('style'), elements = [], element;
      document.documentElement.firstChild.appendChild(style);
      document._qsa = [];

      style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
      window.scrollBy(0, 0);
      style.parentNode.removeChild(style);

      while (document._qsa.length) {
        element = document._qsa.shift();
        element.style.removeAttribute('x-qsa');
        elements.push(element);
      }
      document._qsa = null;
      return elements;
    };
  }

  // document.getElementsByClassName
  if (typeof document.getElementsByClassName!='function') {
      document.getElementsByClassName = function() {
          var elms = document.getElementsByTagName('*');
          var ei = new Array();
          for (i=0;i<elms.length;i++) {
              if (elms[i].getAttribute('class')) {
                  ecl = elms[i].getAttribute('class').split(' ');
                  for (j=0;j<ecl.length;j++) {
                      if (ecl[j].toLowerCase() == arguments[0].toLowerCase()) {
                          ei.push(elms[i]);
                      }
                  }
              } else if (elms[i].className) {
                  ecl = elms[i].className.split(' ');
                  for (j=0;j<ecl.length;j++) {
                      if (ecl[j].toLowerCase() == arguments[0].toLowerCase()) {
                          ei.push(elms[i]);
                      }
                  }
              }
          }
          return ei;
      }
  }

  // [].map
  if (!Array.prototype.map) {
    Array.prototype.map = function (fun /*, thisp */) {
      "use strict";

      if (this === void 0 || this === null) { throw new TypeError(); }

      var t = Object(this);
      var len = t.length >>> 0;
      if (typeof fun !== "function") { throw new TypeError(); }

      var res = []; res.length = len;
      var thisp = arguments[1], i;
      for (i = 0; i < len; i++) {
        if (i in t) {
          res[i] = fun.call(thisp, t[i], i, t);
        }
      }

      return res;
    };
  }

  // [].indexOf
  if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
      "use strict";

      if (this === void 0 || this === null) { throw new TypeError(); }

      var t = Object(this);
      var len = t.length >>> 0;
      if (len === 0) { return -1; }

      var n = 0;
      if (arguments.length > 0) {
        n = Number(arguments[1]);
        if (isNaN(n)) {
          n = 0;
        } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
        }
      }

      if (n >= len) { return -1; }

      var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);

      for (; k < len; k++) {
        if (k in t && t[k] === searchElement) {
          return k;
        }
      }
      return -1;
    };
  }

  // Binds a function constructor to global object
  window.GeoKey = GeoKey;
})(window, document);<|endoftext|>


<commit_before>var $0   = require('path').basename(process.argv[1]);
var args = process.argv.slice(2);

var opts = {
  f: '360p',
  bg: 'blue',
  fg: 'white'
};
var keys = Object.keys(opts);

for (var i = args.length - 1; i > -1; i--) {
  if (!args[i]) continue;
  switch (args[i]) {
  case '-h':
    var l = console.log;
    l('Usage: ' + $0 + ' [OPTION]');
    l();
    l('  -h           show this content');
    l('  -f <format>  use this video format, default is 360p');
    l('  -bg <color>  set background color, default is blue');
    l('  -fg <color>  set foreground color, default is white');
    l();
    l('  format: 240p, 360p, 480p, 720p, 1080p');
    l('  color: black, red, green, yellow, blue, magenta, cyan, white, 0-255');
    process.exit(0);
  default:
    var isarg = false;
    for (var j = 0; j < keys.length; j++) {
      if (args[i - 1] === '-' + keys[j]) {
        opts[keys[j]] = sanitize(args[i]);
        args.splice(i - 1, 2);
        isarg = true;
        break;
      } else if (args[i] === '-' + keys[j]) {
        if (isStr(opts[keys[j]])) {
          error('empty value for option "' + args[i] + '"');
        }
      }
    }
    if (isarg) continue;
    error('unknown option "' + args[i] + '"');
  }
}

module.exports = opts;

function error(e) {
  console.error(e);
  process.exit(1);
}

function isStr(what) {
  return typeof what === 'string';
}

function sanitize(what) {
  if (/^[0-9]+$/.test(what)) return +what;
  return what;
}
<commit_msg>add -v to show version;<commit_after>var $0   = require('path').basename(process.argv[1]);
var args = process.argv.slice(2);

var opts = {
  f: '360p',
  bg: 'blue',
  fg: 'white'
};
var keys = Object.keys(opts);

for (var i = args.length - 1; i > -1; i--) {
  if (!args[i]) continue;
  switch (args[i]) {
  case '-h':
    var l = console.log;
    l('Usage: ' + $0 + ' [OPTION]');
    l();
    l('  -h           show this content and exit');
    l('  -v           print version number and exit');
    l('  -f <format>  use this video format, default is 360p');
    l('  -bg <color>  set background color, default is blue');
    l('  -fg <color>  set foreground color, default is white');
    l();
    l('  format: 240p, 360p, 480p, 720p, 1080p');
    l('  color: black, red, green, yellow, blue, magenta, cyan, white, 0-255');
    process.exit(0);
  case '-v':
    console.log(require('./package.json').version);
    process.exit(0);
  default:
    var isarg = false;
    for (var j = 0; j < keys.length; j++) {
      if (args[i - 1] === '-' + keys[j]) {
        opts[keys[j]] = sanitize(args[i]);
        args.splice(i - 1, 2);
        isarg = true;
        break;
      } else if (args[i] === '-' + keys[j]) {
        if (isStr(opts[keys[j]])) {
          error('empty value for option "' + args[i] + '"');
        }
      }
    }
    if (isarg) continue;
    error('unknown option "' + args[i] + '"');
  }
}

module.exports = opts;

function error(e) {
  console.error(e);
  process.exit(1);
}

function isStr(what) {
  return typeof what === 'string';
}

function sanitize(what) {
  if (/^[0-9]+$/.test(what)) return +what;
  return what;
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

var os = require('os');
var fse = require('fs-extra');
var argv = require('yargs').argv;
var path = require('path');
var child = require('child_process');
var onDeath = require('death')({uncaughtException: false});

var programs = {
	ffmpeg: {
		win32: path.join(__dirname, 'libs', 'ffmpeg.exe'),
		any: 'ffmpeg'
	},
	convert: {
		win32: path.join(__dirname, 'libs', 'convert.exe'),
		any: 'convert'
	}
};

var opts = {
	tmp: path.join(os.tmpdir(), 'giffler-frames-'+randomInt()),
	input: argv.input? argv.input : null,
	output: argv.output || argv.input? path.basename(argv.input) + '.gif' : '',
	loops: argv.loops || 0,
	fps: argv.fps || 30,
	fuzz: argv.fuzz || 0.75, // lower means less artifacts bigger filesize
	dither: argv.dither? 'FloydSteinberg' : 'none',
	memory: argv.memory
};

if (opts.input) gif();
else fail('Missing input, use --input');
onDeath(function() {
	// TODO this isn't pretty
	// onDeath() attaches listeners, this causes fail() to trigger automatically
	console.log('Aborting giffler task...');
});

function gif() {
	removeTempDirectory();
	makeTempDirectory();
}

function removeTempDirectory() {
	fse.removeSync(opts.tmp);
}

function makeTempDirectory() {
	fse.mkdir(opts.tmp, function(err) {
		if (err) fail('Could not create a temporary directory.');
		makeFrames();
	});
}

function makeFrames() {
	var program = programs.ffmpeg[process.platform] || programs.ffmpeg.any;
	var args = [
		'-i', opts.input,
		'-vf',
		'fps=' + opts.fps,
		path.join(opts.tmp, 'f%07d.png')
	];

	console.log('Extracting frames...');
	child.execFile(program, args, function(err) {
		if (err) fail(err);
		makeGif()
	});
}

function makeGif() {
	var program = programs.convert[process.platform] || programs.convert.any;
	var args  = [
		opts.memory? '-limit memory ' + opts.memory + 'MiB' : '',
		'-delay ' + (100 / opts.fps),
		'-loop ' + opts.loops,
		path.join(opts.tmp, 'f*.png'),
		'-dither ' + opts.dither,
		'-fuzz ' + opts.fuzz + '%',
		'-coalesce',
		'-layers optimize',
		opts.output
	];

	// An empty argument '' can cause execFile to hang so remove them...
	args = args.filter(function(e) {return e;}).join(' ').split(' ');

	console.log('Making gif...');
	child.execFile(program, args, function(err) {
		if (err) fail('convert error: ' + err.toString());
		removeTempDirectory();
		console.log('Done!');
	});
}

function randomInt() {
	var low =   10000000000;
	var high =  100000000000;
	return Math.floor(Math.random() * (high - low) + low);
}

function fail(msg) {
	removeTempDirectory();
	console.error(msg);
	printHelp();
	process.exit(1);
}

function printHelp() {
	console.log('');
	console.log(' giffler!');
	console.log('');
	console.log('  required:');
	console.log('    --input <filename>');
	console.log('');
	console.log('  optional and their defaults:');
	console.log('    --fps 30');
	console.log('    --loops 0');
	console.log('    --fuzz 0.75');
	console.log('    --dither none');
	console.log('    --output <same as input plus .gif>');
	console.log('');
	console.log('  explained:');
	console.log('    --loops:');
	console.log('        the number of times the gif should loop');
	console.log('        zero means infinite');
	console.log('     --fuzz:');
	console.log('        number 0-100, representing a percentage');
	console.log('        colors near each other will be considered the same');
	console.log('        higher values cause more artifacts and lower file size');
	console.log('        recommended values are between 0.5 and 5');
	console.log('    --dither:');
	console.log('        opposite of --fuzz but they can be used together');
	console.log('        sprinkles the image with dots');
	console.log('        helps with gradients and photographs');
	console.log('        increases file size');
	console.log('        absent parameter means it\'s off');
	console.log('        tip: keep a low fuzz when using dither or they cancel each other');
}<commit_msg>Change optimization mode, enable dither by default<commit_after>#!/usr/bin/env node

var os = require('os');
var fse = require('fs-extra');
var argv = require('yargs').argv;
var path = require('path');
var child = require('child_process');
var onDeath = require('death')({uncaughtException: false});

var programs = {
	ffmpeg: {
		win32: path.join(__dirname, 'libs', 'ffmpeg.exe'),
		any: 'ffmpeg'
	},
	convert: {
		win32: path.join(__dirname, 'libs', 'convert.exe'),
		any: 'convert'
	}
};

var opts = {
	tmp: path.join(os.tmpdir(), 'giffler-frames-'+randomInt()),
	input: argv.input? argv.input : null,
	output: argv.output || argv.input? path.basename(argv.input) + '.gif' : '',
	loops: argv.loops || 0,
	fps: argv.fps || 30,
	fuzz: argv.fuzz || 0.75, // lower means less artifacts bigger filesize
	dither: typeof argv.dither === 'string'? argv.dither : 'FloydSteinberg',
	memory: argv.memory
};

if (opts.input) gif();
else fail('Missing input, use --input');
onDeath(function() {
	// TODO this isn't pretty
	// onDeath() attaches listeners, this causes fail() to trigger automatically
	console.log('Aborting giffler task...');
});

function gif() {
	removeTempDirectory();
	makeTempDirectory();
}

function removeTempDirectory() {
	fse.removeSync(opts.tmp);
}

function makeTempDirectory() {
	fse.mkdir(opts.tmp, function(err) {
		if (err) fail('Could not create a temporary directory.');
		makeFrames();
	});
}

function makeFrames() {
	var program = programs.ffmpeg[process.platform] || programs.ffmpeg.any;
	var args = [
		'-i', opts.input,
		'-vf',
		'fps=' + opts.fps,
		path.join(opts.tmp, 'f%07d.png')
	];

	console.log('Extracting frames...');
	child.execFile(program, args, function(err) {
		if (err) fail(err);
		makeGif()
	});
}

function makeGif() {
	var program = programs.convert[process.platform] || programs.convert.any;
	var args  = [
		opts.memory? '-limit memory ' + opts.memory + 'MiB' : '',
		'-delay ' + (100 / opts.fps),
		'-loop ' + opts.loops,
		path.join(opts.tmp, 'f*.png'),
		'-dither ' + opts.dither,
		'-fuzz ' + opts.fuzz + '%',
		'-coalesce',
		'-layers optimize-transparency',
		opts.output
	];

	// An empty argument '' can cause execFile to hang so remove them...
	args = args.filter(function(e) {return e;}).join(' ').split(' ');

	console.log('Making gif...');
	child.execFile(program, args, function(err) {
		if (err) fail('convert error: ' + err.toString());
		removeTempDirectory();
		console.log('Done!');
	});
}

function randomInt() {
	var low =   10000000000;
	var high =  100000000000;
	return Math.floor(Math.random() * (high - low) + low);
}

function fail(msg) {
	removeTempDirectory();
	console.error(msg);
	printHelp();
	process.exit(1);
}

function printHelp() {
	console.log('');
	console.log(' giffler!');
	console.log('');
	console.log('  required:');
	console.log('    --input <filename>');
	console.log('');
	console.log('  optional and their defaults:');
	console.log('    --fps 30');
	console.log('    --loops 0');
	console.log('    --fuzz 0.75');
	console.log('    --dither FloydSteinberg');
	console.log('    --output <same as input plus .gif>');
	console.log('');
	console.log('  explained:');
	console.log('    --loops:');
	console.log('        the number of times the gif should loop');
	console.log('        zero means infinite');
	console.log('     --fuzz:');
	console.log('        number 0-100, representing a percentage of color variation');
	console.log('        colors near each other will be considered the same');
	console.log('        higher values reduce file size but cause artifacts');
	console.log('        keep low for animations (0.75), and higher for film (2-10)');
	console.log('    --dither:');
	console.log('        on by default, turn off with --dither none');
	console.log('        helps gradients and film greatly by intentionally adding noise');
	console.log('        should be disabled for animations and screencasts');
}<|endoftext|>