<commit_before>'use strict';

// Load modules
const _ = require('lodash');
const path = require('path');
const moment = require('moment');
const shortid = require('shortid');
const root = '..';

const logger = require(path.join(root, '../utils/logger'));
const { Command } = require(path.join(root, 'command'));
const responseHandler = require(path.join(root, '../bot/response-handler'));

const DEFAULT_CONFIG = {
  alertParams: ['SETUP', 'LIST'],
  algo: {
    CUMULATIVE_DIFFERENCE: 'CUMULATIVE_DIFFERENCE',
    CONSISTENT_VARIATION: 'CONSISTENT_VARIATION',
  },
};

/**
*
* Represents the state and events of an alert command.
*
*/
const Alert = class extends Command {
  /**
  * Creates a new Alert instance.
  * @param {object} options command config.
  * @param {object} options.context command context.
  * @param {string} options.commandName command name.
  * @param {function} options.getBotConfig function to get bot config.
  * @param {function} options.getSlackData function to get slack data.
  * @param {function} options.getHttpAgent function to get http proxy agent.
  * @param {function} options.getHook function to get command hook.
  * @param {function} options.getEventStore function to get event store.
  * @param {function} options.messageHandler function to dispatch message.
  * @return {object} instance of this.
  * @class
  */
  constructor (options) {
    super(options);

    return this;
  }

  /**
  * Function to handle pre-process for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  preprocess (parsedMessage) {
    return Promise.resolve({
      then: (onFulfill, onReject) => {
        if (this.getParams(parsedMessage, 0) ===
          DEFAULT_CONFIG.alertParams[0]) {
          const currentParams = (this.getParams(parsedMessage) || []).join('');

          parsedMessage.scheduleId = parsedMessage.scheduleId ||
            _.toLower(shortid.generate());

          this.alertTaskChannelPath = [this.commandName, 'tasks'];
          this.alertTaskTimerPath = [this.commandName, 'timer'];

          const alertEvents = _.get(this.getEventStore().get(),
            this.alertTaskChannelPath);

          const isAlertExist = _.filter(_.values(alertEvents), (alertEvent) => {
            return alertEvent && _.isEqual(currentParams, (this.getParams(
              alertEvent.parsedMessage) || []).join('')) &&
              parsedMessage.channel === alertEvent.parsedMessage.channel;
          });

          if (isAlertExist.length > 0) {
            this.messageHandler({
              channels: parsedMessage.channel,
              message: responseHandler.generateBotResponseTemplate({
                parsedMessage: parsedMessage,
                alertExist: true,
                commandName: this.commandName,
                botName: this.getEventStore().botName,
              }),
              thread: parsedMessage.thread_ts,
            });

            return onReject(new Error('Alert with arguments already exist'));
          }

          let time = this.getParams(parsedMessage, 0);
          time = _.isNumber(time) && time > 0 ? time : 1;
          if (this.getCommand().timeUnit === 'h') {
            time = time * 3600000;
          } else {
            time = time * 60000; // default to minute
          }

          this.setTimer(parsedMessage, this.alertTaskTimerPath, time);
          this.setupAlertTypeSpecifics(parsedMessage);
        }

        return onFulfill(parsedMessage);
      },
    });
  }

  /**
  * Function to respond to command message.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @return {object} Promise resolves to success or failure.
  * @override
  */
  respond (parsedMessage) {
    if (this.isScheduleList(parsedMessage)) {
      return this.process(parsedMessage).catch((err) => {
        logger.error('Error processing command ', err);
      });
    }

    return this.preprocess(parsedMessage)
      .then(() => {
        return this.setEvent(parsedMessage);
      }).then(() => {
        return this.notify(parsedMessage);
      }).then(() => {
        return this.process(parsedMessage);
      }).catch((err) => {
        logger.error('Error processing command ', err);
      });
  }

  /**
  * Function to handle process for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  process (parsedMessage) {
    return Promise.resolve({
      then: (onFulfill, onReject) => {
        if (this.isScheduleList(parsedMessage)) {
          const scheduledEvents = this.getScheduledEvents(parsedMessage);
          const noEvents = (scheduledEvents || []).length < 1;

          this.messageHandler({
            channels: parsedMessage.channel,
            message: responseHandler.generateBotResponseTemplate({
              parsedMessage: parsedMessage,
              noAlertEvents: noEvents,
              alertEvents: scheduledEvents,
              commandName: this.commandName,
              botName: this.getEventStore().botName,
            }),
            thread: parsedMessage.thread_ts,
          });

          return onFulfill();
        }

        const alertEvents = _.get(this.getEventStore().get(),
          this.alertTaskChannelPath);

        const callback = (eventKey, err, data) => {
          this.message.apply(this, [err, data, eventKey]);
        };

        _.each(_.values(alertEvents), (alertEvent) => {
          const shouldSnooze = this.getCommand().snooze.apply(this, [{
            command: alertEvent.parsedMessage.message.command,
            params: alertEvent.parsedMessage.message.params,
          }]);

          if (shouldSnooze) {
            logger.log('Snoozing alert', { time: new Date() });
            return;
          }

          alertEvent.handler.apply(this, [{
            command: alertEvent.parsedMessage.message.command,
            params: alertEvent.parsedMessage.message.params,
          }, this.buildOptions(alertEvent.parsedMessage,
            this.getSlackData(), this.purpose), _.partial(callback,
            alertEvent.parsedMessage.scheduleId)]);
        });

        return onFulfill();
      },
    });
  }

  /**
  * Function to handle vaidation for schedule command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  validate (parsedMessage) {
    const task = this.getParams(parsedMessage, 0);

    if (task === 'LIST') {
      return Promise.resolve();
    }

    this.setDefaultParams(this.getCommand(),
      parsedMessage);

    const allowedParamValid = this.validateCommandArgs(
      this.getCommand(), parsedMessage);

    if (allowedParamValid.isNoop || allowedParamValid.isValid) {
      return Promise.resolve();
    }

    if (!allowedParamValid.isNoop && !allowedParamValid.isValid) {
      return Promise.reject({
        param: true,
        mParams: allowedParamValid.isMultiParam,
        failedParams: allowedParamValid.failedParams,
        sampleParams: allowedParamValid.sampleParams,
        noOfErrors: allowedParamValid.noOfErrors,
        parsedMessage,
      });
    }

    return Promise.resolve();
  }

  /**
  * Function to handle notify for alert command.
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  notify (parsedMessage) {
    return Promise.resolve({
      then: (onFulfill) => {
        if (this.getParams(parsedMessage, 0) ===
          DEFAULT_CONFIG.alertParams[0]) {
          this.messageHandler({
            channels: parsedMessage.channel,
            message: responseHandler.generateBotResponseTemplate({
              template: _.isFunction(this.template) ? this.template : '',
              /* jshint ignore:start */
              alertNotification: true,
              /* jshint ignore:end */
              commandName: this.commandName,
              botName: this.getEventStore().botName,
              threshold: this.getParams(parsedMessage, 1) || 75,
              thread: parsedMessage.thread_ts,
            }),
          });
        }

        onFulfill();
      },
    });
  }

  /**
  * Function to handle message for alert command.
  *
  * @param {object} err err from alert data handler.
  * @param {object} data result from alert data handler.
  * @override
  */
  message (err, data, eventKey) {
    if (err) {
      logger.info('Error from alert handler', err);
      return;
    }

    this.handleAlertMessage(data, eventKey);
  }

  /**
  * Function to handle reload command for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  reloadCommand (parsedMessage) {
    this.preprocess(parsedMessage)
      .catch((err) => {
        logger.info('Error processing command ', err);
      });
  }

  /**
  * Function to handle alert algo.
  * @param {object} parsedMessage Message returned @link command/message.js.
  */
  setupAlertTypeSpecifics (parsedMessage) {
    const sensitivity = this.getParams(parsedMessage, 1) || 75;
    let events = {};
    const channels = {};

    _.set(channels, parsedMessage.scheduleId, {
      parsedMessage: parsedMessage,
      sensitivity: sensitivity,
      commandType: this.getCommand().commandType,
      handler: this.getCommand().data,
    });
    events = _.merge({}, _.get(this.getEventStore().get(),
      this.alertTaskChannelPath, {}), channels);

    this.getEventStore().set(this.alertTaskChannelPath, events);
  }

  /**
  * Function to handle alert algo.
  * @param {object} data result from alert data handler.
  * @param {string} eventKey event key for the alert
  */
  handleAlertMessage (data, eventKey) {
    if (this.getCommand().algo === DEFAULT_CONFIG.algo.CUMULATIVE_DIFFERENCE) {
      this.handleCumulativeDifference(data, eventKey);
    } else if (this.getCommand().algo ===
      DEFAULT_CONFIG.algo.CONSISTENT_VARIATION) {
      this.handleVariationDifference(data, eventKey);
    }
  }

  /**
  * Function to compute CUMULATIVE_DIFFERENCE algo.
  * @param {object} data result from alert data handler.
  * @param {string} eventKey event key for the alert
  */
  handleCumulativeDifference (data, eventKey) {
    if (data) {
      const alertData = _.get(this.getEventStore().get(),
        _.concat(this.alertTaskChannelPath, eventKey));
      const sensitivity = _.get(alertData, 'sensitivity');
      const isPercentage = _.endsWith(sensitivity, '%');
      const cResult = computeCumulativeDifference(data,
        _.get(alertData, 'previousDataSet'), isPercentage);

      alertData.previousDataSet = cResult.previousDataSet;
      this.getEventStore().set(_.concat(this.alertTaskChannelPath, eventKey),
        alertData);

      const result = _.extend({
        template: this.template,
        cumulativeDifferenceAlert: true,
        time: new Date().toString(),
        params: alertData.parsedMessage.message.params,
      }, cResult, data);

      if (Math.abs(result.difference) > parseInt(sensitivity, 10)) {
        this.messageHandler({
          channels: [alertData.parsedMessage.channel],
          message: responseHandler.generateAlertResponseTemplate(result),
        });
      }
    }
  }

  /**
  * Function to compute CONSISTENT_VARIATION algo.
  * @param {object} data result from alert data handler.
  * @param {string} eventKey event key for the alert
  */
  handleVariationDifference (data, eventKey) {
    let alertData;
    if (data && data.length > 0) {
      const varianceResult = calculateVariance(data);

      if (varianceResult && varianceResult.perct) {
        alertData = _.get(this, _.concat(this.alertTaskChannelPath, eventKey));
      }

      if (varianceResult.perct > _.get(alertData, 'sensitivity')) {
        this.messageHandler({
          channels: [alertData.parsedMessage.channel],
          message: responseHandler.generateAlertResponseTemplate(_.extend({
            template: this.template,
            consistentVariationAlert: true,
            dataset: data,
            perct: varianceResult.perct,
            time: new Date().toString(),
            params: alertData.parsedMessage.message.params,
          }, data)),
        });
      }
    }
  }

  /**
  * Function to handle set timer for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @param {object} path path of timer.
  * @param {object} timeInterval time interval to call quietRespond.
  * @override
  */
  setTimer (parsedMessage, path, timeInterval) {
    const timer = this.getTimer(parsedMessage, path);

    if (!timer) {
      this.getEventStore().set(path, setInterval(() => {
        this.quietRespond(parsedMessage);
      }, timeInterval));
    }
  }

  /**
  * Function to set event for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  setEvent (parsedMessage) {
    return this.getEventStore().update({
      eventType: 'events',
      id: parsedMessage.scheduleId,
    }, {
      parsedMessage: parsedMessage,
      channels: [parsedMessage.channel],
    }).then(() => parsedMessage);
  }
};

/**
* Function to compute CUMULATIVE_DIFFERENCE algo.
* @param {object} timeSeriesDataSet Array of time and value pair.
* @param {object} previousDataSet Pervious data set for comparision.
* @param {boolean} isPercentage flag to calculate percentage difference.
* @return {object} result of the difference between last two data sets.
*/
const computeCumulativeDifference =
  function (timeSeriesDataSet, previousDataSet, isPercentage) {
    const sortedDataSetByTime = _.sortBy(timeSeriesDataSet, 'time');
    const lastPreviousData = _.last(previousDataSet) ||
      _.last(sortedDataSetByTime);
    const currentDataTime = _.get(
      _.last(sortedDataSetByTime), 'time');
    const isFuture = currentDataTime ? moment.unix(currentDataTime)
      .isAfter(moment.unix(lastPreviousData.time)) : false;
    let result = {};

    if (lastPreviousData && isFuture) {
      const lastValue = _.last(sortedDataSetByTime).value;
      const firstValue = _.first(sortedDataSetByTime).value;
      const difference = lastValue - firstValue;
      const isHigher = lastValue > firstValue;

      if (isPercentage) {
        const differencePerct = Math.abs(Math.round(difference /
          firstValue * 100));

        result = {
          dataset: timeSeriesDataSet,
          difference: differencePerct,
          unit: '%',
        };
      } else {
        result = {
          dataset: timeSeriesDataSet,
          difference: Math.abs(difference),
        };
      }

      if (isHigher) {
        result.isHigher = true;
      } else {
        result.isLower = true;
      }
    }

    result.previousDataSet = _.size(sortedDataSetByTime) > 0 ?
      sortedDataSetByTime : previousDataSet;

    return result;
  };

/**
* Function to compute variance for dataset.
*
* @param {array} dataSetArr alert input data set.
* @return {object} computed percentage difference.
*/
const calculateVariance = function (dataSetArr) {
  const dataSetLength = _.isArray(dataSetArr) ? dataSetArr.length : 0;
  let sensitivityPercentage = 0;

  if (dataSetLength > 0 && dataSetLength % 2 === 0) {
    const sdSet1 = Math.abs(standardDeviation(
      _.slice(dataSetArr, 0, (dataSetLength / 2))));
    const sdSet2 = Math.abs(standardDeviation(
      _.slice(dataSetArr, dataSetLength / 2)));

    if (sdSet1 !== 0 && sdSet2 !== 0) {
      if (sdSet1 > sdSet2) {
        sensitivityPercentage = Math.floor((sdSet1 - sdSet2) / sdSet2 * 100);
      } else {
        sensitivityPercentage = Math.floor((sdSet2 - sdSet1) / sdSet1 * 100);
      }
      if (sensitivityPercentage > 0) {
        return {
          perct: sensitivityPercentage,
        };
      }
    }
  }
};

/**
  http://derickbailey.com/2014/09/21/
  calculating-standard-deviation-with-array-map-
  and-array-reduce-in-javascript/
*/

/**
* Function to compute standard deviation.
*
* @param {array} values alert input data set.
* @return {string} computed standar deviation.
*/
const standardDeviation = function (values) {
  const avg = average(values);
  const squareDiffs = values.map(function (value) {
    const diff = value - avg;
    return diff * diff;
  });

  return Math.sqrt(average(squareDiffs));
};

/**
* Function to compute average.
*
* @param {array} values alert input data set.
* @return {string} computed average.
*/
const average = function (values) {
  const sum = values.reduce(function (sum, value) {
    return sum + value;
  }, 0);

  return sum / values.length;
};

module.exports = {
  Alert,
};
<commit_msg>alert: check snooze type<commit_after>'use strict';

// Load modules
const _ = require('lodash');
const path = require('path');
const moment = require('moment');
const shortid = require('shortid');
const root = '..';

const logger = require(path.join(root, '../utils/logger'));
const { Command } = require(path.join(root, 'command'));
const responseHandler = require(path.join(root, '../bot/response-handler'));

const DEFAULT_CONFIG = {
  alertParams: ['SETUP', 'LIST'],
  algo: {
    CUMULATIVE_DIFFERENCE: 'CUMULATIVE_DIFFERENCE',
    CONSISTENT_VARIATION: 'CONSISTENT_VARIATION',
  },
};

/**
*
* Represents the state and events of an alert command.
*
*/
const Alert = class extends Command {
  /**
  * Creates a new Alert instance.
  * @param {object} options command config.
  * @param {object} options.context command context.
  * @param {string} options.commandName command name.
  * @param {function} options.getBotConfig function to get bot config.
  * @param {function} options.getSlackData function to get slack data.
  * @param {function} options.getHttpAgent function to get http proxy agent.
  * @param {function} options.getHook function to get command hook.
  * @param {function} options.getEventStore function to get event store.
  * @param {function} options.messageHandler function to dispatch message.
  * @return {object} instance of this.
  * @class
  */
  constructor (options) {
    super(options);

    return this;
  }

  /**
  * Function to handle pre-process for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  preprocess (parsedMessage) {
    return Promise.resolve({
      then: (onFulfill, onReject) => {
        if (this.getParams(parsedMessage, 0) ===
          DEFAULT_CONFIG.alertParams[0]) {
          const currentParams = (this.getParams(parsedMessage) || []).join('');

          parsedMessage.scheduleId = parsedMessage.scheduleId ||
            _.toLower(shortid.generate());

          this.alertTaskChannelPath = [this.commandName, 'tasks'];
          this.alertTaskTimerPath = [this.commandName, 'timer'];

          const alertEvents = _.get(this.getEventStore().get(),
            this.alertTaskChannelPath);

          const isAlertExist = _.filter(_.values(alertEvents), (alertEvent) => {
            return alertEvent && _.isEqual(currentParams, (this.getParams(
              alertEvent.parsedMessage) || []).join('')) &&
              parsedMessage.channel === alertEvent.parsedMessage.channel;
          });

          if (isAlertExist.length > 0) {
            this.messageHandler({
              channels: parsedMessage.channel,
              message: responseHandler.generateBotResponseTemplate({
                parsedMessage: parsedMessage,
                alertExist: true,
                commandName: this.commandName,
                botName: this.getEventStore().botName,
              }),
              thread: parsedMessage.thread_ts,
            });

            return onReject(new Error('Alert with arguments already exist'));
          }

          let time = this.getParams(parsedMessage, 0);
          time = _.isNumber(time) && time > 0 ? time : 1;
          if (this.getCommand().timeUnit === 'h') {
            time = time * 3600000;
          } else {
            time = time * 60000; // default to minute
          }

          this.setTimer(parsedMessage, this.alertTaskTimerPath, time);
          this.setupAlertTypeSpecifics(parsedMessage);
        }

        return onFulfill(parsedMessage);
      },
    });
  }

  /**
  * Function to respond to command message.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @return {object} Promise resolves to success or failure.
  * @override
  */
  respond (parsedMessage) {
    if (this.isScheduleList(parsedMessage)) {
      return this.process(parsedMessage).catch((err) => {
        logger.error('Error processing command ', err);
      });
    }

    return this.preprocess(parsedMessage)
      .then(() => {
        return this.setEvent(parsedMessage);
      }).then(() => {
        return this.notify(parsedMessage);
      }).then(() => {
        return this.process(parsedMessage);
      }).catch((err) => {
        logger.error('Error processing command ', err);
      });
  }

  /**
  * Function to handle process for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  process (parsedMessage) {
    return Promise.resolve({
      then: (onFulfill, onReject) => {
        if (this.isScheduleList(parsedMessage)) {
          const scheduledEvents = this.getScheduledEvents(parsedMessage);
          const noEvents = (scheduledEvents || []).length < 1;

          this.messageHandler({
            channels: parsedMessage.channel,
            message: responseHandler.generateBotResponseTemplate({
              parsedMessage: parsedMessage,
              noAlertEvents: noEvents,
              alertEvents: scheduledEvents,
              commandName: this.commandName,
              botName: this.getEventStore().botName,
            }),
            thread: parsedMessage.thread_ts,
          });

          return onFulfill();
        }

        const alertEvents = _.get(this.getEventStore().get(),
          this.alertTaskChannelPath);

        const callback = (eventKey, err, data) => {
          this.message.apply(this, [err, data, eventKey]);
        };

        _.each(_.values(alertEvents), (alertEvent) => {
          if (typeof this.getCommand().snooze === 'function') {
            const shouldSnooze = this.getCommand().snooze.apply(this, [{
              command: alertEvent.parsedMessage.message.command,
              params: alertEvent.parsedMessage.message.params,
            }]);

            if (shouldSnooze) {
              logger.log('Snoozing alert', { time: new Date() });
              return;
            }
          }

          alertEvent.handler.apply(this, [{
            command: alertEvent.parsedMessage.message.command,
            params: alertEvent.parsedMessage.message.params,
          }, this.buildOptions(alertEvent.parsedMessage,
            this.getSlackData(), this.purpose), _.partial(callback,
            alertEvent.parsedMessage.scheduleId)]);
        });

        return onFulfill();
      },
    });
  }

  /**
  * Function to handle vaidation for schedule command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  validate (parsedMessage) {
    const task = this.getParams(parsedMessage, 0);

    if (task === 'LIST') {
      return Promise.resolve();
    }

    this.setDefaultParams(this.getCommand(),
      parsedMessage);

    const allowedParamValid = this.validateCommandArgs(
      this.getCommand(), parsedMessage);

    if (allowedParamValid.isNoop || allowedParamValid.isValid) {
      return Promise.resolve();
    }

    if (!allowedParamValid.isNoop && !allowedParamValid.isValid) {
      return Promise.reject({
        param: true,
        mParams: allowedParamValid.isMultiParam,
        failedParams: allowedParamValid.failedParams,
        sampleParams: allowedParamValid.sampleParams,
        noOfErrors: allowedParamValid.noOfErrors,
        parsedMessage,
      });
    }

    return Promise.resolve();
  }

  /**
  * Function to handle notify for alert command.
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  notify (parsedMessage) {
    return Promise.resolve({
      then: (onFulfill) => {
        if (this.getParams(parsedMessage, 0) ===
          DEFAULT_CONFIG.alertParams[0]) {
          this.messageHandler({
            channels: parsedMessage.channel,
            message: responseHandler.generateBotResponseTemplate({
              template: _.isFunction(this.template) ? this.template : '',
              /* jshint ignore:start */
              alertNotification: true,
              /* jshint ignore:end */
              commandName: this.commandName,
              botName: this.getEventStore().botName,
              threshold: this.getParams(parsedMessage, 1) || 75,
              thread: parsedMessage.thread_ts,
            }),
          });
        }

        onFulfill();
      },
    });
  }

  /**
  * Function to handle message for alert command.
  *
  * @param {object} err err from alert data handler.
  * @param {object} data result from alert data handler.
  * @override
  */
  message (err, data, eventKey) {
    if (err) {
      logger.info('Error from alert handler', err);
      return;
    }

    this.handleAlertMessage(data, eventKey);
  }

  /**
  * Function to handle reload command for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  reloadCommand (parsedMessage) {
    this.preprocess(parsedMessage)
      .catch((err) => {
        logger.info('Error processing command ', err);
      });
  }

  /**
  * Function to handle alert algo.
  * @param {object} parsedMessage Message returned @link command/message.js.
  */
  setupAlertTypeSpecifics (parsedMessage) {
    const sensitivity = this.getParams(parsedMessage, 1) || 75;
    let events = {};
    const channels = {};

    _.set(channels, parsedMessage.scheduleId, {
      parsedMessage: parsedMessage,
      sensitivity: sensitivity,
      commandType: this.getCommand().commandType,
      handler: this.getCommand().data,
    });
    events = _.merge({}, _.get(this.getEventStore().get(),
      this.alertTaskChannelPath, {}), channels);

    this.getEventStore().set(this.alertTaskChannelPath, events);
  }

  /**
  * Function to handle alert algo.
  * @param {object} data result from alert data handler.
  * @param {string} eventKey event key for the alert
  */
  handleAlertMessage (data, eventKey) {
    if (this.getCommand().algo === DEFAULT_CONFIG.algo.CUMULATIVE_DIFFERENCE) {
      this.handleCumulativeDifference(data, eventKey);
    } else if (this.getCommand().algo ===
      DEFAULT_CONFIG.algo.CONSISTENT_VARIATION) {
      this.handleVariationDifference(data, eventKey);
    }
  }

  /**
  * Function to compute CUMULATIVE_DIFFERENCE algo.
  * @param {object} data result from alert data handler.
  * @param {string} eventKey event key for the alert
  */
  handleCumulativeDifference (data, eventKey) {
    if (data) {
      const alertData = _.get(this.getEventStore().get(),
        _.concat(this.alertTaskChannelPath, eventKey));
      const sensitivity = _.get(alertData, 'sensitivity');
      const isPercentage = _.endsWith(sensitivity, '%');
      const cResult = computeCumulativeDifference(data,
        _.get(alertData, 'previousDataSet'), isPercentage);

      alertData.previousDataSet = cResult.previousDataSet;
      this.getEventStore().set(_.concat(this.alertTaskChannelPath, eventKey),
        alertData);

      const result = _.extend({
        template: this.template,
        cumulativeDifferenceAlert: true,
        time: new Date().toString(),
        params: alertData.parsedMessage.message.params,
      }, cResult, data);

      if (Math.abs(result.difference) > parseInt(sensitivity, 10)) {
        this.messageHandler({
          channels: [alertData.parsedMessage.channel],
          message: responseHandler.generateAlertResponseTemplate(result),
        });
      }
    }
  }

  /**
  * Function to compute CONSISTENT_VARIATION algo.
  * @param {object} data result from alert data handler.
  * @param {string} eventKey event key for the alert
  */
  handleVariationDifference (data, eventKey) {
    let alertData;
    if (data && data.length > 0) {
      const varianceResult = calculateVariance(data);

      if (varianceResult && varianceResult.perct) {
        alertData = _.get(this, _.concat(this.alertTaskChannelPath, eventKey));
      }

      if (varianceResult.perct > _.get(alertData, 'sensitivity')) {
        this.messageHandler({
          channels: [alertData.parsedMessage.channel],
          message: responseHandler.generateAlertResponseTemplate(_.extend({
            template: this.template,
            consistentVariationAlert: true,
            dataset: data,
            perct: varianceResult.perct,
            time: new Date().toString(),
            params: alertData.parsedMessage.message.params,
          }, data)),
        });
      }
    }
  }

  /**
  * Function to handle set timer for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @param {object} path path of timer.
  * @param {object} timeInterval time interval to call quietRespond.
  * @override
  */
  setTimer (parsedMessage, path, timeInterval) {
    const timer = this.getTimer(parsedMessage, path);

    if (!timer) {
      this.getEventStore().set(path, setInterval(() => {
        this.quietRespond(parsedMessage);
      }, timeInterval));
    }
  }

  /**
  * Function to set event for alert command.
  *
  * @param {object} parsedMessage Message returned @link command/message.js.
  * @override
  */
  setEvent (parsedMessage) {
    return this.getEventStore().update({
      eventType: 'events',
      id: parsedMessage.scheduleId,
    }, {
      parsedMessage: parsedMessage,
      channels: [parsedMessage.channel],
    }).then(() => parsedMessage);
  }
};

/**
* Function to compute CUMULATIVE_DIFFERENCE algo.
* @param {object} timeSeriesDataSet Array of time and value pair.
* @param {object} previousDataSet Pervious data set for comparision.
* @param {boolean} isPercentage flag to calculate percentage difference.
* @return {object} result of the difference between last two data sets.
*/
const computeCumulativeDifference =
  function (timeSeriesDataSet, previousDataSet, isPercentage) {
    const sortedDataSetByTime = _.sortBy(timeSeriesDataSet, 'time');
    const lastPreviousData = _.last(previousDataSet) ||
      _.last(sortedDataSetByTime);
    const currentDataTime = _.get(
      _.last(sortedDataSetByTime), 'time');
    const isFuture = currentDataTime ? moment.unix(currentDataTime)
      .isAfter(moment.unix(lastPreviousData.time)) : false;
    let result = {};

    if (lastPreviousData && isFuture) {
      const lastValue = _.last(sortedDataSetByTime).value;
      const firstValue = _.first(sortedDataSetByTime).value;
      const difference = lastValue - firstValue;
      const isHigher = lastValue > firstValue;

      if (isPercentage) {
        const differencePerct = Math.abs(Math.round(difference /
          firstValue * 100));

        result = {
          dataset: timeSeriesDataSet,
          difference: differencePerct,
          unit: '%',
        };
      } else {
        result = {
          dataset: timeSeriesDataSet,
          difference: Math.abs(difference),
        };
      }

      if (isHigher) {
        result.isHigher = true;
      } else {
        result.isLower = true;
      }
    }

    result.previousDataSet = _.size(sortedDataSetByTime) > 0 ?
      sortedDataSetByTime : previousDataSet;

    return result;
  };

/**
* Function to compute variance for dataset.
*
* @param {array} dataSetArr alert input data set.
* @return {object} computed percentage difference.
*/
const calculateVariance = function (dataSetArr) {
  const dataSetLength = _.isArray(dataSetArr) ? dataSetArr.length : 0;
  let sensitivityPercentage = 0;

  if (dataSetLength > 0 && dataSetLength % 2 === 0) {
    const sdSet1 = Math.abs(standardDeviation(
      _.slice(dataSetArr, 0, (dataSetLength / 2))));
    const sdSet2 = Math.abs(standardDeviation(
      _.slice(dataSetArr, dataSetLength / 2)));

    if (sdSet1 !== 0 && sdSet2 !== 0) {
      if (sdSet1 > sdSet2) {
        sensitivityPercentage = Math.floor((sdSet1 - sdSet2) / sdSet2 * 100);
      } else {
        sensitivityPercentage = Math.floor((sdSet2 - sdSet1) / sdSet1 * 100);
      }
      if (sensitivityPercentage > 0) {
        return {
          perct: sensitivityPercentage,
        };
      }
    }
  }
};

/**
  http://derickbailey.com/2014/09/21/
  calculating-standard-deviation-with-array-map-
  and-array-reduce-in-javascript/
*/

/**
* Function to compute standard deviation.
*
* @param {array} values alert input data set.
* @return {string} computed standar deviation.
*/
const standardDeviation = function (values) {
  const avg = average(values);
  const squareDiffs = values.map(function (value) {
    const diff = value - avg;
    return diff * diff;
  });

  return Math.sqrt(average(squareDiffs));
};

/**
* Function to compute average.
*
* @param {array} values alert input data set.
* @return {string} computed average.
*/
const average = function (values) {
  const sum = values.reduce(function (sum, value) {
    return sum + value;
  }, 0);

  return sum / values.length;
};

module.exports = {
  Alert,
};
<|endoftext|>


<commit_before>const Jimp = require('jimp');
const { range } = require('lodash');

const { intToRGBA, rgbaToInt } = Jimp;

/**
 * Creates a Promise wrapper around Jimp's constructor.
 *
 * @param {number} width - The image width in pixels
 * @param {number} height - The image height in pixels
 * @return {Promise} - Promise resolving to a Jimp object
 */
const createImage = function (width, height) {
    return new Promise((resolve, reject) => {
        // eslint-disable-next-line no-new
        new Jimp(width, height, (err, image) => {
            if (err) {
                return reject(err);
            }

            return resolve(image);
        });
    });
};

/**
 * Converts color range of form 0.0-1.0 to 0-255
 *
 * @param {Object} pixel    Object describing the pixel colors in range 0-1
 * @param {number} pixel.r  The red channel
 * @param {number} pixel.g  The green channel
 * @param {number} pixel.b  The blue channel
 * @param {number} pixel.a  The alpha channel
 * @return {Object} The object describing the pixel colors in range 0-255
 */
const convertColorToDecimalRange = function ({ r, g, b, a }) {
    return {
        r: r / 255,
        g: g / 255,
        b: b / 255,
        a: a / 255,
    };
};

/**
 * Converts color range of form 0-255 to 0.0-1.0
 *
 * @param {Object} pixel    Object describing the pixel colors in range 0-255
 * @param {number} pixel.r  The red channel
 * @param {number} pixel.g  The green channel
 * @param {number} pixel.b  The blue channel
 * @param {number} pixel.a  The alpha channel
 * @return {Object} The object describing the pixel colors in range 0-1
 */
const convertColorTo255Range = function ({ r, g, b, a }) {
    return {
        r: Math.round(r * 255),
        g: Math.round(g * 255),
        b: Math.round(b * 255),
        a: Math.round(a * 255),
    };
};

/**
 * Applies a blending of color channels
 *
 * @param {Jimp}    The Jimp image object to apply blending
 * @return {Jimp}   The transformed Jimp image object
 */
const blend = function (image) {
    const { width, height } = image.bitmap;

    range(width).forEach((col) => {
        range(height).forEach((row) => {
            const { a, r, g, b } =
                convertColorToDecimalRange(intToRGBA(image.getPixelColor(col, row)));

            if (a !== 0) {
                const pixel = convertColorTo255Range({
                    r: r * a,
                    g: g * a,
                    b: b * a,
                    a: (r + g + b) / 3,
                });

                image.setPixelColor(rgbaToInt(pixel.r, pixel.g, pixel.b, pixel.a), col, row);
            }
        });
    });

    return image;
};

// mask : hex
// color : hex
// findColor : boolean

// attempt to implement haxe's
// function getColorBoundsRect( mask : UInt, color : UInt, ?findColor : Bool ) : Rectangle
/*
   Determines a rectangular region that either fully encloses all pixels of a specified color
   within the bitmap image (if the findColor parameter is set to true) or fully encloses all pixels
   that do not include the specified color (if the findColor parameter is set to false).

   For example, if you have a source image and you want to determine the rectangle of the image
   that contains a nonzero alpha channel, pass {mask: 0xFF000000, color: 0x00000000} as parameters.
   If the findColor parameter is set to true, the entire image is searched for the bounds of pixels
   for which (value & mask) == color (where value is the color value of the pixel). If the
   findColor parameter is set to false, the entire image is searched for the bounds of pixels for
   which (value & mask) != color (where value is the color value of the pixel). To determine white
   space around an image, pass {mask: 0xFFFFFFFF, color: 0xFFFFFFFF} to find the bounds of nonwhite
   pixels.
*/
const getColorBoundsRect = function (image, mask, color, findColor) {
    // findColor : value & mask === color
    // !findColor : value & mask !== color
    const { width, height } = image.bitmap;

    const extremities = range(height).reduce((acc, row) =>
        range(width).reduce((obj, col) => {
            const pixelColor = image.getPixelColor(col, row);

            // eslint-disable-next-line no-bitwise
            if ((findColor && (pixelColor & mask === color)) || (pixelColor & mask !== color)) {
                const { minx, miny, maxx, maxy } = obj;
                return {
                    minx: col < minx ? col : minx,
                    miny: row < miny ? row : miny,
                    maxx: col > maxx ? col : maxx,
                    maxy: row > maxy ? row : maxy,
                };
            }
            return obj;
        }, acc)
    , { minx: width, maxx: 0, miny: height, maxy: 0 });

    const { minx: x, miny: y, maxx, maxy } = extremities;

    return {
        x,
        y,
        width: Math.max(0, (maxx - x) + 1),
        height: Math.max(0, (maxy - y) + 1),
    };
};

// export default {
module.exports = {
    createImage,
    blend,
    getColorBoundsRect,
};
<commit_msg>style: fix lint errors<commit_after>const Jimp = require('jimp');
const { range } = require('lodash');

const { intToRGBA, rgbaToInt } = Jimp;

/**
 * Creates a Promise wrapper around Jimp's constructor.
 *
 * @param {number} width - The image width in pixels
 * @param {number} height - The image height in pixels
 * @return {Promise} - Promise resolving to a Jimp object
 */
const createImage = function (width, height) {
    return new Promise((resolve, reject) => {
        // eslint-disable-next-line no-new
        new Jimp(width, height, (err, image) => {
            if (err) {
                return reject(err);
            }

            return resolve(image);
        });
    });
};

/**
 * Converts color range of form 0.0-1.0 to 0-255
 *
 * @param {Object} pixel    Object describing the pixel colors in range 0-1
 * @param {number} pixel.r  The red channel
 * @param {number} pixel.g  The green channel
 * @param {number} pixel.b  The blue channel
 * @param {number} pixel.a  The alpha channel
 * @return {Object} The object describing the pixel colors in range 0-255
 */
const convertColorToDecimalRange = function ({
    r, g, b, a,
}) {
    return {
        r: r / 255,
        g: g / 255,
        b: b / 255,
        a: a / 255,
    };
};

/**
 * Converts color range of form 0-255 to 0.0-1.0
 *
 * @param {Object} pixel    Object describing the pixel colors in range 0-255
 * @param {number} pixel.r  The red channel
 * @param {number} pixel.g  The green channel
 * @param {number} pixel.b  The blue channel
 * @param {number} pixel.a  The alpha channel
 * @return {Object} The object describing the pixel colors in range 0-1
 */
const convertColorTo255Range = function ({
    r, g, b, a,
}) {
    return {
        r: Math.round(r * 255),
        g: Math.round(g * 255),
        b: Math.round(b * 255),
        a: Math.round(a * 255),
    };
};

/**
 * Applies a blending of color channels
 *
 * @param {Jimp}    The Jimp image object to apply blending
 * @return {Jimp}   The transformed Jimp image object
 */
const blend = function (image) {
    const { width, height } = image.bitmap;

    range(width).forEach((col) => {
        range(height).forEach((row) => {
            const {
                a, r, g, b,
            } = convertColorToDecimalRange(intToRGBA(image.getPixelColor(col, row)));

            if (a !== 0) {
                const pixel = convertColorTo255Range({
                    r: r * a,
                    g: g * a,
                    b: b * a,
                    a: (r + g + b) / 3,
                });

                image.setPixelColor(rgbaToInt(pixel.r, pixel.g, pixel.b, pixel.a), col, row);
            }
        });
    });

    return image;
};

// mask : hex
// color : hex
// findColor : boolean

// attempt to implement haxe's
// function getColorBoundsRect( mask : UInt, color : UInt, ?findColor : Bool ) : Rectangle
/*
   Determines a rectangular region that either fully encloses all pixels of a specified color
   within the bitmap image (if the findColor parameter is set to true) or fully encloses all pixels
   that do not include the specified color (if the findColor parameter is set to false).

   For example, if you have a source image and you want to determine the rectangle of the image
   that contains a nonzero alpha channel, pass {mask: 0xFF000000, color: 0x00000000} as parameters.
   If the findColor parameter is set to true, the entire image is searched for the bounds of pixels
   for which (value & mask) == color (where value is the color value of the pixel). If the
   findColor parameter is set to false, the entire image is searched for the bounds of pixels for
   which (value & mask) != color (where value is the color value of the pixel). To determine white
   space around an image, pass {mask: 0xFFFFFFFF, color: 0xFFFFFFFF} to find the bounds of nonwhite
   pixels.
*/
const getColorBoundsRect = function (image, mask, color, findColor) {
    // findColor : value & mask === color
    // !findColor : value & mask !== color
    const { width, height } = image.bitmap;

    const extremities = range(height).reduce((acc, row) => range(width).reduce((obj, col) => {
        const pixelColor = image.getPixelColor(col, row);

        // eslint-disable-next-line no-bitwise
        if ((findColor && (pixelColor & mask === color)) || (pixelColor & mask !== color)) {
            const {
                minx, miny, maxx, maxy,
            } = obj;
            return {
                minx: col < minx ? col : minx,
                miny: row < miny ? row : miny,
                maxx: col > maxx ? col : maxx,
                maxy: row > maxy ? row : maxy,
            };
        }
        return obj;
    }, acc), {
        minx: width, maxx: 0, miny: height, maxy: 0,
    });

    const {
        minx: x, miny: y, maxx, maxy,
    } = extremities;

    return {
        x,
        y,
        width: Math.max(0, (maxx - x) + 1),
        height: Math.max(0, (maxy - y) + 1),
    };
};

// export default {
module.exports = {
    createImage,
    blend,
    getColorBoundsRect,
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { windowResize, changeBackgroundMode, changeCurrentViewFont, changeHeaderMode, changeHeaderCollapsedTop, changeAnimationIdx, changeAnimationScriptIdx } from '../actions';
import { Header, HeaderCollapsed, FontsList, Description, Footer, NewsfeedLoader, FontCSSLoader, HeaderGutter, GoogleFontBadge } from './';
import scrollama from 'scrollama';
import { scaleLinear } from 'd3';
import 'gsap';
import paper from 'paper';
import { isTouchDevice } from '../utils';

const Fragment = React.Fragment;

class App extends Component {
  constructor(props){
    super(props);
    this.handleResize = this.handleResize.bind(this);
    this.handleScroll = this.handleScroll.bind(this);

    this.headerTopScale = scaleLinear().domain([62, 0]).clamp(true).range([0, -62]);

    this.scroller = scrollama();
    this.handleBodyClick = this.handleBodyClick.bind(this);
  }

  componentWillMount(){
    window.addEventListener('resize', this.handleResize);
    window.addEventListener('scroll', this.handleScroll);
    this.handleResize();

  }

  componentDidMount(){
    // document.getElementById("root").addEventListener('click', this.handleBodyClick.bind(this), false);
    this.initScroll();
    this.handleScroll();
  }

  handleBodyClick(e){
    let { isOnScript, animationIdx, animationScriptIdx } = this.props;

    if (!isTouchDevice()){
      this.props.dispatch(changeBackgroundMode(this.props.backgroundMode == "black" ? "white" : "black"));
      if (isOnScript) {

        this.props.dispatch(changeAnimationScriptIdx(++animationScriptIdx % 3));

      } else {

        this.props.dispatch(changeAnimationIdx(++animationIdx % 5));

      }

    }

  }

  componentWillReceiveProps(newProps){
    if (newProps.currentCategory != this.props.currentCategory) {
      this.moveScroll(newProps.currentCategory);
    }
  }

  moveScroll(currentCategory){
    let { headerHeight } = this.props;

    let collapsedHeaderHeight;
    if (!_.isNull(document.querySelector(".header-collapsed"))) {
      collapsedHeaderHeight = document.querySelector(".header-collapsed").offsetHeight;
    } else {
      collapsedHeaderHeight = 62;
    }
    let categoryPosTop = document.querySelector(`a[name=category-${currentCategory}]`).offsetTop;
    let offset = 20;

    TweenMax.to((document.documentElement || document.body.parentNode || document.body), 1, { ease: Power3.easeInOut, scrollTop: categoryPosTop - collapsedHeaderHeight - offset - headerHeight });
  }

  handleScroll(e){
    // console.log(window.scrollTop);
    var descTop = document.querySelector('.description').getBoundingClientRect().top;
    var scrollY = window.scrollY;

    // console.log("descTop: ", descTop);
    if (descTop < 62 && descTop >= 0) {

      this.props.dispatch(changeHeaderCollapsedTop(this.headerTopScale(descTop)));

    } else {

      this.props.dispatch(changeHeaderCollapsedTop(0));

    }

    if (descTop < 0) {

      this.props.dispatch(changeHeaderMode("black"));

    } else {

      if (scrollY < 10) {

        this.props.dispatch(changeHeaderMode("expanded"));

      } else if (scrollY >= 10) {

        this.props.dispatch(changeHeaderMode("collapsed"));

      }
    }

  }

  initScroll(){
    this.scroller.setup({
        step: '.font-viewer',
        // debug: true,
        // progress: true,
        offset: 200 / this.props.screenHeight
      }).onStepEnter(this.handleStepEnter.bind(this))
        // .onStepProgress(this.handleStepProgress.bind(this))
        .onStepExit(this.handleStepExit.bind(this));
  }

  handleStepEnter(e){
    if (this.props.currentViewFont != Number(e.element.dataset.id)) {
      this.props.dispatch(changeCurrentViewFont(Number(e.element.dataset.id)));
    }
  }

  handleStepExit(e){
    if (window.scrollY < this.props.headerHeight) {
      this.props.dispatch(changeCurrentViewFont(1));
    } else {
      this.props.dispatch(changeCurrentViewFont(null));

    }

  }

  updateBackground(newProps){

  }

  componentWillUnmount(){
    window.removeEventListener('resize', this.handleResize);
    window.removeEventListener('scroll', this.handleScroll);
  }

  handleResize(e){
    this.scroller.resize();
    this.props.dispatch(windowResize(window.innerWidth, window.innerHeight));
  }

  render() {
    let { headerMode, backgroundMode } = this.props;

    return (
      <Fragment>

          <NewsfeedLoader />
          {
            headerMode == "expanded" ?
             : (headerMode == "collapsed" ? <HeaderCollapsed /> : null)
          }
          <HeaderGutter />
          <FontsList />

            <Description />

          <FontCSSLoader />

        <GoogleFontBadge />
        <link rel="stylesheet" media="all" href={`./public/style_${backgroundMode}.css`} async />
      </Fragment>
    );
  }
}

let mapStateToProps = state => {
  return {
    headerMode: state.headerMode,
    screenWidth: state.screenWidth,
    screenHeight: state.screenHeight,
    currentCategory: state.currentCategory,
    currentViewFont: state.currentViewFont,
    headerHeight: state.headerHeight,
    locale: state.locale,
    backgroundMode: state.backgroundMode,
    isOnScript: state.isOnScript,
    animationIdx: state.animationIdx,
    animationScriptIdx: state.animationScriptIdx
  }
}

export default connect(mapStateToProps)(App);<commit_msg>mobile scrolling error testing<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { windowResize, changeBackgroundMode, changeCurrentViewFont, changeHeaderMode, changeHeaderCollapsedTop, changeAnimationIdx, changeAnimationScriptIdx } from '../actions';
import { Header, HeaderCollapsed, FontsList, Description, Footer, NewsfeedLoader, FontCSSLoader, HeaderGutter, GoogleFontBadge } from './';
import scrollama from 'scrollama';
import { scaleLinear } from 'd3';
import 'gsap';
import paper from 'paper';
import { isTouchDevice } from '../utils';

const Fragment = React.Fragment;

class App extends Component {
  constructor(props){
    super(props);
    this.handleResize = this.handleResize.bind(this);
    this.handleScroll = this.handleScroll.bind(this);

    this.headerTopScale = scaleLinear().domain([62, 0]).clamp(true).range([0, -62]);

    this.scroller = scrollama();
    this.handleBodyClick = this.handleBodyClick.bind(this);
  }

  componentWillMount(){
    window.addEventListener('resize', this.handleResize);
    window.addEventListener('scroll', this.handleScroll);
    this.handleResize();

  }

  componentDidMount(){
    // document.getElementById("root").addEventListener('click', this.handleBodyClick.bind(this), false);
    this.initScroll();
    this.handleScroll();
  }

  handleBodyClick(e){
    let { isOnScript, animationIdx, animationScriptIdx } = this.props;

    if (!isTouchDevice()){
      this.props.dispatch(changeBackgroundMode(this.props.backgroundMode == "black" ? "white" : "black"));
      if (isOnScript) {

        this.props.dispatch(changeAnimationScriptIdx(++animationScriptIdx % 3));

      } else {

        this.props.dispatch(changeAnimationIdx(++animationIdx % 5));

      }

    }

  }

  componentWillReceiveProps(newProps){
    if (newProps.currentCategory != this.props.currentCategory) {
      this.moveScroll(newProps.currentCategory);
    }
  }

  moveScroll(currentCategory){
    let { headerHeight } = this.props;

    let collapsedHeaderHeight;
    if (!_.isNull(document.querySelector(".header-collapsed"))) {
      collapsedHeaderHeight = document.querySelector(".header-collapsed").offsetHeight;
    } else {
      collapsedHeaderHeight = 62;
    }
    let categoryPosTop = document.querySelector(`a[name=category-${currentCategory}]`).offsetTop;
    let offset = 20;
    debugger;
    TweenMax.to((html || document.documentElement || document.body.parentNode || document.body), 1, { ease: Power3.easeInOut, scrollTop: categoryPosTop - collapsedHeaderHeight - offset - headerHeight });
  }

  handleScroll(e){
    // console.log(window.scrollTop);
    var descTop = document.querySelector('.description').getBoundingClientRect().top;
    var scrollY = window.scrollY;

    // console.log("descTop: ", descTop);
    if (descTop < 62 && descTop >= 0) {

      this.props.dispatch(changeHeaderCollapsedTop(this.headerTopScale(descTop)));

    } else {

      this.props.dispatch(changeHeaderCollapsedTop(0));

    }

    if (descTop < 0) {

      this.props.dispatch(changeHeaderMode("black"));

    } else {

      if (scrollY < 10) {

        this.props.dispatch(changeHeaderMode("expanded"));

      } else if (scrollY >= 10) {

        this.props.dispatch(changeHeaderMode("collapsed"));

      }
    }

  }

  initScroll(){
    this.scroller.setup({
        step: '.font-viewer',
        // debug: true,
        // progress: true,
        offset: 200 / this.props.screenHeight
      }).onStepEnter(this.handleStepEnter.bind(this))
        // .onStepProgress(this.handleStepProgress.bind(this))
        .onStepExit(this.handleStepExit.bind(this));
  }

  handleStepEnter(e){
    if (this.props.currentViewFont != Number(e.element.dataset.id)) {
      this.props.dispatch(changeCurrentViewFont(Number(e.element.dataset.id)));
    }
  }

  handleStepExit(e){
    if (window.scrollY < this.props.headerHeight) {
      this.props.dispatch(changeCurrentViewFont(1));
    } else {
      this.props.dispatch(changeCurrentViewFont(null));

    }

  }

  updateBackground(newProps){

  }

  componentWillUnmount(){
    window.removeEventListener('resize', this.handleResize);
    window.removeEventListener('scroll', this.handleScroll);
  }

  handleResize(e){
    this.scroller.resize();
    this.props.dispatch(windowResize(window.innerWidth, window.innerHeight));
  }

  render() {
    let { headerMode, backgroundMode } = this.props;

    return (
      <Fragment>

          <NewsfeedLoader />
          {
            headerMode == "expanded" ?
             : (headerMode == "collapsed" ? <HeaderCollapsed /> : null)
          }
          <HeaderGutter />
          <FontsList />

            <Description />

          <FontCSSLoader />

        <GoogleFontBadge />
        <link rel="stylesheet" media="all" href={`./public/style_${backgroundMode}.css`} async />
      </Fragment>
    );
  }
}

let mapStateToProps = state => {
  return {
    headerMode: state.headerMode,
    screenWidth: state.screenWidth,
    screenHeight: state.screenHeight,
    currentCategory: state.currentCategory,
    currentViewFont: state.currentViewFont,
    headerHeight: state.headerHeight,
    locale: state.locale,
    backgroundMode: state.backgroundMode,
    isOnScript: state.isOnScript,
    animationIdx: state.animationIdx,
    animationScriptIdx: state.animationScriptIdx
  }
}

export default connect(mapStateToProps)(App);<|endoftext|>


<commit_before>import {Map} from 'immutable';
import {createReducer} from 'redux-immutablejs';

const LIST_FOLDERS = 'garchen-panel/folder/LIST_FOLDERS';
const LIST_FOLDERS_SUCCESS = 'garchen-panel/folder/LIST_FOLDERS_SUCCESS';
const LIST_FOLDERS_FAIL = 'garchen-panel/folder/LIST_FOLDERS_FAIL';

const ADD_FOLDER = 'garchen-panel/folder/ADD_FOLDER';
const ADD_FOLDER_SUCCESS = 'garchen-panel/folder/ADD_FOLDER_SUCCESS';
const ADD_FOLDER_FAIL = 'garchen-panel/folder/ADD_FOLDER_FAIL';

const GET_FOLDER = 'garchen-panel/folder/GET_FOLDER';
const GET_FOLDER_SUCCESS = 'garchen-panel/folder/GET_FOLDER_SUCCESS';
const GET_FOLDER_FAIL = 'garchen-panel/folder/GET_FOLDER_FAIL';

const UPDATE_FOLDER = 'garchen-panel/folder/UPDATE_FOLDER';
const UPDATE_FOLDER_SUCCESS = 'garchen-panel/folder/UPDATE_FOLDER_SUCCESS';
const UPDATE_FOLDER_FAIL = 'garchen-panel/folder/UPDATE_FOLDER_FAIL';

const CHECK_FOLDER_EXISTS = 'garchen-panel/folder/CHECK_FOLDER_EXISTS';
const CHECK_FOLDER_EXISTS_SUCCESS = 'garchen-panel/folder/CHECK_FOLDER_EXISTS_SUCCESS';
const CHECK_FOLDER_EXISTS_FAIL = 'garchen-panel/folder/CHECK_FOLDER_EXISTS_FAIL';

const DELETE_FOLDER = 'garchen-panel/folder/DELETE_FOLDER';
const DELETE_FOLDER_SUCCESS = 'garchen-panel/folder/DELETE_FOLDER_SUCCESS';
const DELETE_FOLDER_FAIL = 'garchen-panel/folder/DELETE_FOLDER_FAIL';

const ADD_FOLDER_BY_CSV = 'garchen-panel/folder/ADD_FOLDER_BY_CSV';
const ADD_FOLDER_BY_CSV_SUCCESS = 'garchen-panel/folder/ADD_FOLDER_BY_CSV_SUCCESS';
const ADD_FOLDER_BY_CSV_FAIL = 'garchen-panel/folder/ADD_FOLDER_BY_CSV_FAIL';

const CANCEL_IMPORTING_CSV = 'garchen-panel/folder/CANCEL_IMPORTING_CSV';
const CANCEL_IMPORTING_CSV_SUCCESS = 'garchen-panel/folder/CANCEL_IMPORTING_CSV_SUCCESS';
const CANCEL_IMPORTING_CSV_FAIL = 'garchen-panel/folder/CANCEL_IMPORTING_CSV_FAIL';

const SET_IMPORTING_FOLDER_ID = 'garchen-panel/folder/SET_IMPORTING_FOLDER_ID';

const FOLDER_PERPAGE = 20;

const initialState = Map({
  perpage: FOLDER_PERPAGE,
  folder: null,
  folders: [],
  folderCount: 0,
  isProcessingCsv: false,
  errorCsvMessage: null,
  errorCsvMessageId: null,
  errorCsvFilename: null,
  importingFolderId: null
});

export default createReducer(initialState, {

  [GET_FOLDER_SUCCESS]: (state, action) => {
    return state.set('folder', action.result);
  },

  [LIST_FOLDERS_SUCCESS]: (state, action) => {
    return state.set('folders', action.result.data)
      .set('folderCount', action.result.total);
  },

  [ADD_FOLDER_BY_CSV]: (state) => {
    return state.set('isProcessingCsv', true)
      .set('errorCsvMessage', null)
      .set('errorCsvMessageId', null)
      .set('errorCsvFilename', null);
  },

  [ADD_FOLDER_BY_CSV_SUCCESS]: (state) => {
    return state.set('isProcessingCsv', false)
      .set('importingFolderId', null);
  },

  [ADD_FOLDER_BY_CSV_FAIL]: (state, action) => {
    const {messageId, filename, message} = action.error;
    return state.set('isProcessingCsv', false)
      .set('importingFolderId', null)
      .set('errorCsvMessage', message)
      .set('errorCsvMessageId', messageId)
      .set('errorCsvFilename', filename);
  },

  [CANCEL_IMPORTING_CSV_SUCCESS]: (state) => {
    return state.set('isProcessingCsv', false)
      .set('importingFolderId', null)
      .set('errorCsvMessage', null)
      .set('errorCsvMessageId', null)
      .set('errorCsvFilename', null);
  },

  [SET_IMPORTING_FOLDER_ID]: (state, action) => {
    return state.set('importingFolderId', action.folderId);
  }
});

export function setImportingFolderId(folderId) {
  return {
    type: SET_IMPORTING_FOLDER_ID,
    folderId
  };
}

export function getFolder(data) {
  return {
    types: [GET_FOLDER, GET_FOLDER_SUCCESS, GET_FOLDER_FAIL],
    promise: (client) => client.send('get-folder', data)
  };
}

export function listFolders(data) {
  return {
    types: [LIST_FOLDERS, LIST_FOLDERS_SUCCESS, LIST_FOLDERS_FAIL],
    promise: (client) => client.send('list-folders', data)
  };
}

export function addFolder(data) {
  return {
    types: [ADD_FOLDER, ADD_FOLDER_SUCCESS, ADD_FOLDER_FAIL],
    promise: (client) => {
      return client.send('add-folder', data);
    }
  };
}

export function updateFolder(data) {
  return {
    types: [UPDATE_FOLDER, UPDATE_FOLDER_SUCCESS, UPDATE_FOLDER_FAIL],
    promise: (client) => {
      return client.send('update-folder', data);
    }
  };
}

export function deleteFolder(data) {
  return {
    types: [DELETE_FOLDER, DELETE_FOLDER_SUCCESS, DELETE_FOLDER_FAIL],
    promise: (client) => {
      return client.send('delete-folder', data);
    }
  };
}

export function checkFolderExists(data) {
  return {
    types: [CHECK_FOLDER_EXISTS, CHECK_FOLDER_EXISTS_SUCCESS, CHECK_FOLDER_EXISTS_FAIL],
    promise: (client) => {
      return client.send('check-folder-exists', data);
    }
  };
}

export function cancelImportingCsv() {
  return {
    types: [CANCEL_IMPORTING_CSV, CANCEL_IMPORTING_CSV_SUCCESS, CANCEL_IMPORTING_CSV_FAIL],
    promise: (client) => {
      return client.send('cancel-importing-csv');
    }
  };
}

export function addFolderByCsv(writeDelay) {
  return {
    types: [ADD_FOLDER_BY_CSV, ADD_FOLDER_BY_CSV_SUCCESS, ADD_FOLDER_BY_CSV_FAIL],
    promise: (client) => {
      return client.send('add-folder-by-csv', {writeDelay});
    }
  };
}
<commit_msg>add function setDisplayFolderPerPage to folder duck<commit_after>import {Map} from 'immutable';
import {createReducer} from 'redux-immutablejs';

const LIST_FOLDERS = 'garchen-panel/folder/LIST_FOLDERS';
const LIST_FOLDERS_SUCCESS = 'garchen-panel/folder/LIST_FOLDERS_SUCCESS';
const LIST_FOLDERS_FAIL = 'garchen-panel/folder/LIST_FOLDERS_FAIL';

const ADD_FOLDER = 'garchen-panel/folder/ADD_FOLDER';
const ADD_FOLDER_SUCCESS = 'garchen-panel/folder/ADD_FOLDER_SUCCESS';
const ADD_FOLDER_FAIL = 'garchen-panel/folder/ADD_FOLDER_FAIL';

const GET_FOLDER = 'garchen-panel/folder/GET_FOLDER';
const GET_FOLDER_SUCCESS = 'garchen-panel/folder/GET_FOLDER_SUCCESS';
const GET_FOLDER_FAIL = 'garchen-panel/folder/GET_FOLDER_FAIL';

const UPDATE_FOLDER = 'garchen-panel/folder/UPDATE_FOLDER';
const UPDATE_FOLDER_SUCCESS = 'garchen-panel/folder/UPDATE_FOLDER_SUCCESS';
const UPDATE_FOLDER_FAIL = 'garchen-panel/folder/UPDATE_FOLDER_FAIL';

const CHECK_FOLDER_EXISTS = 'garchen-panel/folder/CHECK_FOLDER_EXISTS';
const CHECK_FOLDER_EXISTS_SUCCESS = 'garchen-panel/folder/CHECK_FOLDER_EXISTS_SUCCESS';
const CHECK_FOLDER_EXISTS_FAIL = 'garchen-panel/folder/CHECK_FOLDER_EXISTS_FAIL';

const DELETE_FOLDER = 'garchen-panel/folder/DELETE_FOLDER';
const DELETE_FOLDER_SUCCESS = 'garchen-panel/folder/DELETE_FOLDER_SUCCESS';
const DELETE_FOLDER_FAIL = 'garchen-panel/folder/DELETE_FOLDER_FAIL';

const ADD_FOLDER_BY_CSV = 'garchen-panel/folder/ADD_FOLDER_BY_CSV';
const ADD_FOLDER_BY_CSV_SUCCESS = 'garchen-panel/folder/ADD_FOLDER_BY_CSV_SUCCESS';
const ADD_FOLDER_BY_CSV_FAIL = 'garchen-panel/folder/ADD_FOLDER_BY_CSV_FAIL';

const CANCEL_IMPORTING_CSV = 'garchen-panel/folder/CANCEL_IMPORTING_CSV';
const CANCEL_IMPORTING_CSV_SUCCESS = 'garchen-panel/folder/CANCEL_IMPORTING_CSV_SUCCESS';
const CANCEL_IMPORTING_CSV_FAIL = 'garchen-panel/folder/CANCEL_IMPORTING_CSV_FAIL';

const SET_IMPORTING_FOLDER_ID = 'garchen-panel/folder/SET_IMPORTING_FOLDER_ID';

const SET_DISPLAY_FOLDER_PERPAGE = 'garchen-panel/folder/SET_DISPLAY_FOLDER_PERPAGE';

const FOLDER_PERPAGE = 20;

const initialState = Map({
  perpage: FOLDER_PERPAGE,
  folder: null,
  folders: [],
  folderCount: 0,
  isProcessingCsv: false,
  errorCsvMessage: null,
  errorCsvMessageId: null,
  errorCsvFilename: null,
  importingFolderId: null
});

export default createReducer(initialState, {

  [GET_FOLDER_SUCCESS]: (state, action) => {
    return state.set('folder', action.result);
  },

  [LIST_FOLDERS_SUCCESS]: (state, action) => {
    return state.set('folders', action.result.data)
      .set('folderCount', action.result.total);
  },

  [ADD_FOLDER_BY_CSV]: (state) => {
    return state.set('isProcessingCsv', true)
      .set('errorCsvMessage', null)
      .set('errorCsvMessageId', null)
      .set('errorCsvFilename', null);
  },

  [ADD_FOLDER_BY_CSV_SUCCESS]: (state) => {
    return state.set('isProcessingCsv', false)
      .set('importingFolderId', null);
  },

  [ADD_FOLDER_BY_CSV_FAIL]: (state, action) => {
    const {messageId, filename, message} = action.error;
    return state.set('isProcessingCsv', false)
      .set('importingFolderId', null)
      .set('errorCsvMessage', message)
      .set('errorCsvMessageId', messageId)
      .set('errorCsvFilename', filename);
  },

  [CANCEL_IMPORTING_CSV_SUCCESS]: (state) => {
    return state.set('isProcessingCsv', false)
      .set('importingFolderId', null)
      .set('errorCsvMessage', null)
      .set('errorCsvMessageId', null)
      .set('errorCsvFilename', null);
  },

  [SET_IMPORTING_FOLDER_ID]: (state, action) => {
    return state.set('importingFolderId', action.folderId);
  },

  [SET_DISPLAY_FOLDER_PERPAGE]: (state, action) => {
    return state.set('perpage', action.perpage);
  }
});

export function setImportingFolderId(folderId) {
  return {
    type: SET_IMPORTING_FOLDER_ID,
    folderId
  };
}

export function getFolder(data) {
  return {
    types: [GET_FOLDER, GET_FOLDER_SUCCESS, GET_FOLDER_FAIL],
    promise: (client) => client.send('get-folder', data)
  };
}

export function listFolders(data) {
  return {
    types: [LIST_FOLDERS, LIST_FOLDERS_SUCCESS, LIST_FOLDERS_FAIL],
    promise: (client) => client.send('list-folders', data)
  };
}

export function addFolder(data) {
  return {
    types: [ADD_FOLDER, ADD_FOLDER_SUCCESS, ADD_FOLDER_FAIL],
    promise: (client) => {
      return client.send('add-folder', data);
    }
  };
}

export function updateFolder(data) {
  return {
    types: [UPDATE_FOLDER, UPDATE_FOLDER_SUCCESS, UPDATE_FOLDER_FAIL],
    promise: (client) => {
      return client.send('update-folder', data);
    }
  };
}

export function deleteFolder(data) {
  return {
    types: [DELETE_FOLDER, DELETE_FOLDER_SUCCESS, DELETE_FOLDER_FAIL],
    promise: (client) => {
      return client.send('delete-folder', data);
    }
  };
}

export function checkFolderExists(data) {
  return {
    types: [CHECK_FOLDER_EXISTS, CHECK_FOLDER_EXISTS_SUCCESS, CHECK_FOLDER_EXISTS_FAIL],
    promise: (client) => {
      return client.send('check-folder-exists', data);
    }
  };
}

export function cancelImportingCsv() {
  return {
    types: [CANCEL_IMPORTING_CSV, CANCEL_IMPORTING_CSV_SUCCESS, CANCEL_IMPORTING_CSV_FAIL],
    promise: (client) => {
      return client.send('cancel-importing-csv');
    }
  };
}

export function addFolderByCsv(writeDelay) {
  return {
    types: [ADD_FOLDER_BY_CSV, ADD_FOLDER_BY_CSV_SUCCESS, ADD_FOLDER_BY_CSV_FAIL],
    promise: (client) => {
      return client.send('add-folder-by-csv', {writeDelay});
    }
  };
}

export function setDisplayFolderPerPage(perpage) {
  return {
    type: SET_DISPLAY_FOLDER_PERPAGE,
    perpage
  };
}
<|endoftext|>


<commit_before>/*!
Cloud4all Preferences Management Tools

Copyright 2013 CERTH/HIT
Copyright 2014 OCAD University

Licensed under the New BSD license. You may not use this file except in
compliance with this License.

You may obtain a copy of the License at
https://github.com/GPII/prefsEditors/LICENSE.txt
*/

/*global fluid, jQuery, gpii*/
/*jslint white: true, onevar: true, funcinvoke: true, forvar: true, undef: true, newcap: true, nomen: true, regexp: true, plusplus: true, bitwise: true, maxerr: 50, indent: 4 */

(function ($, fluid) {

    fluid.defaults("gpii.adjuster.onOffSwitch", {
        gradeNames: ["fluid.prefs.panel", "autoInit"],
        onOffModelKey: null,    // Must be provided by integrators. The model key that controls on off switch
        selectors: {
            headingLabel: ".gpiic-onOffSwitch-label",
            valueCheckbox: ".gpiic-onOffSwitch-input",
            onOffSwitch: ".gpiic-prefsEditor-onOffSwitch"
        },
        selectorsToIgnore: ["onOffSwitch"],
        listeners: {
            "onCreate.init": {
                listener: "gpii.adjuster.onOffSwitch.init",
                args: ["{that}", "{that}.options.onOffModelKey"]
            },
            "onDomBind.addAria": "gpii.adjuster.onOffSwitch.addAria",
            "onDomBind.makeOnOffSwitchActivatable": {
                "funcName": "fluid.activatable",
                "args": ["{that}.dom.onOffSwitch", "{that}.triggerModelChangeOnActivate"]
            },
            /*
             * TODO: This is a temporary workaround for retaining focus on ON/OFF switch when it is toggled.
             * This listener (along with the called invoker) should not be needed when,
             *      http://issues.fluidproject.org/browse/FLUID-5278
             * is resolved.
             */
            "onDomBind.bindFocusListener": {
                "this": "{that}.dom.valueCheckbox",
                "method": "change",
                args: ["{that}.focus"]
            },
            "onDomBind.setOnText": {
                "this": "{that}.dom.onOffSwitch",
                "method": "attr",
                "args": ["data-checkboxStateOn", "{that}.stringBundle.onText"]
            },
            "onDomBind.setOffText": {
                "this": "{that}.dom.onOffSwitch",
                "method": "attr",
                "args": ["data-checkboxStateOff", "{that}.stringBundle.offText"]
            }
        },
        invokers: {
            triggerModelChangeOnActivate: {
                "funcName": "gpii.adjuster.onOffSwitch.triggerModelChangeOnActivate",
                "args": ["{that}", "{arguments}.0"]
            },
            focus: {
                "this": "{that}.dom.onOffSwitch",
                "method": "trigger",
                args: ["focus"]
            }
        }
    });

    gpii.adjuster.onOffSwitch.triggerModelChangeOnActivate = function (that, event) {
        /*
         * This isn't the most appropriate way of performing the model change compared to requestChange.
         * The reason for this is that the actual model path will be known only to the sub-components
         * of this one (e.g. the magnifierEnabled). So, we either do it this way or we force/anticipate
         * that sub-component implementers will follow the forced "that.model.value" path...
         */
        that.locate("valueCheckbox").click();
        //that.applier.requestChange("value", !that.model.value)

        /*
         * This is needed because most modern browsers have the functionality of scrolling further
         * down when Space key is pressed. So, this is needed in order to suppress this functionality when the
         * on/off switch has focus.
         */
        event.preventDefault();
    };

    gpii.adjuster.onOffSwitch.init = function (that, onOffModelKey) {
        that.applier.modelChanged.addListener(onOffModelKey, function (newModel) {
            var onOffSwitch = that.locate("onOffSwitch");
            onOffSwitch.attr("aria-pressed", newModel[onOffModelKey]);
        });
    };

    gpii.adjuster.onOffSwitch.addAria = function (that) {
        that.container.attr("role", "application");

        var onOffSwitch = that.locate("onOffSwitch");
        onOffSwitch.attr("role", "button");
        onOffSwitch.attr("aria-labelledby", gpii.ariaUtility.getLabelId(that.locate("headingLabel")));
        onOffSwitch.attr("aria-pressed", that.model[that.options.onOffModelKey]);
    };

})(jQuery, fluid);
<commit_msg>GPII-724: The onOffSwitch's aria role is now "checkbox". Since we are backing up those on/off toggles with html checkbox inputs, it looks like screen readers can understand better their role if they are marked as checkboxes instead of buttons.<commit_after>/*!
Cloud4all Preferences Management Tools

Copyright 2013 CERTH/HIT
Copyright 2014 OCAD University

Licensed under the New BSD license. You may not use this file except in
compliance with this License.

You may obtain a copy of the License at
https://github.com/GPII/prefsEditors/LICENSE.txt
*/

/*global fluid, jQuery, gpii*/
/*jslint white: true, onevar: true, funcinvoke: true, forvar: true, undef: true, newcap: true, nomen: true, regexp: true, plusplus: true, bitwise: true, maxerr: 50, indent: 4 */

(function ($, fluid) {

    fluid.defaults("gpii.adjuster.onOffSwitch", {
        gradeNames: ["fluid.prefs.panel", "autoInit"],
        onOffModelKey: null,    // Must be provided by integrators. The model key that controls on off switch
        selectors: {
            headingLabel: ".gpiic-onOffSwitch-label",
            valueCheckbox: ".gpiic-onOffSwitch-input",
            onOffSwitch: ".gpiic-prefsEditor-onOffSwitch"
        },
        selectorsToIgnore: ["onOffSwitch"],
        listeners: {
            "onCreate.init": {
                listener: "gpii.adjuster.onOffSwitch.init",
                args: ["{that}", "{that}.options.onOffModelKey"]
            },
            "onDomBind.addAria": "gpii.adjuster.onOffSwitch.addAria",
            "onDomBind.makeOnOffSwitchActivatable": {
                "funcName": "fluid.activatable",
                "args": ["{that}.dom.onOffSwitch", "{that}.triggerModelChangeOnActivate"]
            },
            /*
             * TODO: This is a temporary workaround for retaining focus on ON/OFF switch when it is toggled.
             * This listener (along with the called invoker) should not be needed when,
             *      http://issues.fluidproject.org/browse/FLUID-5278
             * is resolved.
             */
            "onDomBind.bindFocusListener": {
                "this": "{that}.dom.valueCheckbox",
                "method": "change",
                args: ["{that}.focus"]
            },
            "onDomBind.setOnText": {
                "this": "{that}.dom.onOffSwitch",
                "method": "attr",
                "args": ["data-checkboxStateOn", "{that}.stringBundle.onText"]
            },
            "onDomBind.setOffText": {
                "this": "{that}.dom.onOffSwitch",
                "method": "attr",
                "args": ["data-checkboxStateOff", "{that}.stringBundle.offText"]
            }
        },
        invokers: {
            triggerModelChangeOnActivate: {
                "funcName": "gpii.adjuster.onOffSwitch.triggerModelChangeOnActivate",
                "args": ["{that}", "{arguments}.0"]
            },
            focus: {
                "this": "{that}.dom.onOffSwitch",
                "method": "trigger",
                args: ["focus"]
            }
        }
    });

    gpii.adjuster.onOffSwitch.triggerModelChangeOnActivate = function (that, event) {
        /*
         * This isn't the most appropriate way of performing the model change compared to requestChange.
         * The reason for this is that the actual model path will be known only to the sub-components
         * of this one (e.g. the magnifierEnabled). So, we either do it this way or we force/anticipate
         * that sub-component implementers will follow the forced "that.model.value" path...
         */
        that.locate("valueCheckbox").click();
        //that.applier.requestChange("value", !that.model.value)

        /*
         * This is needed because most modern browsers have the functionality of scrolling further
         * down when Space key is pressed. So, this is needed in order to suppress this functionality when the
         * on/off switch has focus.
         */
        event.preventDefault();
    };

    gpii.adjuster.onOffSwitch.init = function (that, onOffModelKey) {
        that.applier.modelChanged.addListener(onOffModelKey, function (newModel) {
            var onOffSwitch = that.locate("onOffSwitch");
            onOffSwitch.attr("aria-checked", newModel[onOffModelKey]);
        });
    };

    gpii.adjuster.onOffSwitch.addAria = function (that) {
        that.container.attr("role", "application");

        var onOffSwitch = that.locate("onOffSwitch");
        onOffSwitch.attr("role", "checkbox");
        onOffSwitch.attr("aria-labelledby", gpii.ariaUtility.getLabelId(that.locate("headingLabel")));
        onOffSwitch.attr("aria-checked", that.model[that.options.onOffModelKey]);
    };

})(jQuery, fluid);
<|endoftext|>


<commit_before>const log = require('debug')('app:socket:player')
const Queue = require('../Queue')

const {
  REQUEST_PLAYER_NEXT,
  PLAYER_NEXT,
  REQUEST_PLAYER_PLAY,
  PLAYER_PLAY,
  REQUEST_PLAYER_PAUSE,
  PLAYER_PAUSE,
  REQUEST_PLAYER_VOLUME,
  PLAYER_VOLUME,
  EMIT_PLAYER_STATUS,
  PLAYER_STATUS,
  EMIT_PLAYER_ERROR,
  PLAYER_ERROR,
  EMIT_PLAYER_LEAVE,
  PLAYER_LEAVE,
} = require('../../constants/actions')

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [REQUEST_PLAYER_NEXT]: async (sock, { payload }) => {
    // @todo: accept current queueId and pos so server
    // can decide whether to increment song play count
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_NEXT,
      payload: await Queue.getQueue(sock.user.roomId),
    })
  },
  [REQUEST_PLAYER_PLAY]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_PLAY,
    })
  },
  [REQUEST_PLAYER_PAUSE]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_PAUSE,
    })
  },
  [REQUEST_PLAYER_VOLUME]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_VOLUME,
      payload
    })
  },
  [EMIT_PLAYER_STATUS]: async (sock, { payload }) => {
    // so we can tell the room when players leave and
    // relay last known player status on client join
    sock._lastPlayerStatus = payload

    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_STATUS,
      payload,
    })
  },
  [EMIT_PLAYER_LEAVE]: async (sock, { payload }) => {
    sock._lastPlayerStatus = null

    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_LEAVE,
      payload: {
        socketId: sock.id,
      }
    })
  },
  [EMIT_PLAYER_ERROR]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_ERROR,
      payload,
    })
  },
}

module.exports = ACTION_HANDLERS
<commit_msg>Remove old todo<commit_after>const log = require('debug')('app:socket:player')
const Queue = require('../Queue')

const {
  REQUEST_PLAYER_NEXT,
  PLAYER_NEXT,
  REQUEST_PLAYER_PLAY,
  PLAYER_PLAY,
  REQUEST_PLAYER_PAUSE,
  PLAYER_PAUSE,
  REQUEST_PLAYER_VOLUME,
  PLAYER_VOLUME,
  EMIT_PLAYER_STATUS,
  PLAYER_STATUS,
  EMIT_PLAYER_ERROR,
  PLAYER_ERROR,
  EMIT_PLAYER_LEAVE,
  PLAYER_LEAVE,
} = require('../../constants/actions')

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [REQUEST_PLAYER_NEXT]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_NEXT,
      payload: await Queue.getQueue(sock.user.roomId),
    })
  },
  [REQUEST_PLAYER_PLAY]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_PLAY,
    })
  },
  [REQUEST_PLAYER_PAUSE]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_PAUSE,
    })
  },
  [REQUEST_PLAYER_VOLUME]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_VOLUME,
      payload
    })
  },
  [EMIT_PLAYER_STATUS]: async (sock, { payload }) => {
    // so we can tell the room when players leave and
    // relay last known player status on client join
    sock._lastPlayerStatus = payload

    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_STATUS,
      payload,
    })
  },
  [EMIT_PLAYER_LEAVE]: async (sock, { payload }) => {
    sock._lastPlayerStatus = null

    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_LEAVE,
      payload: {
        socketId: sock.id,
      }
    })
  },
  [EMIT_PLAYER_ERROR]: async (sock, { payload }) => {
    sock.server.to(sock.user.roomId).emit('action', {
      type: PLAYER_ERROR,
      payload,
    })
  },
}

module.exports = ACTION_HANDLERS
<|endoftext|>


<commit_before>var MathLib = artifacts.require("./libraries/MathLib.sol");
var OrderLib = artifacts.require("./libraries/OrderLib.sol");
var CollateralToken = artifacts.require("./tokens/CollateralToken.sol");
var MarketContract = artifacts.require("./MarketContract.sol");
var MarketContractRegistry = artifacts.require("./MarketContractRegistry.sol");

// example of deploying a contract using a basic ERC20 token example as collateral
// and uses kraken's ETH/BTC price repeating  the queries every 30 seconds
module.exports = function(deployer) {
    deployer.deploy(MathLib);
    deployer.deploy(OrderLib);
    deployer.deploy(MarketContractRegistry)

    deployer.link(MathLib, MarketContract);
    deployer.link(OrderLib, MarketContract);

    deployer.deploy(CollateralToken).then(function() {
    return deployer.deploy(
                        MarketContract,
                        "ETHXBT",
                        CollateralToken.address,
                        "URL",
                        "json(https://api.kraken.com/0/public/Ticker?pair=ETHUSD).result.XETHZUSD.c.0",
                        30,
                        20155,
                        60465,
                        2,
                        10,
                        60 * 15,
                        {gas:6600000 ,  value: web3.toWei('.2', 'ether'), from: web3.eth.accounts[0]})
    })

    // add deployed contract to whitelist.
    deployer.then(function() {
        return MarketContractRegistry.deployed();
    }).then(function (instance) {
        instance.addAddressToWhiteList(MarketContract.address);
    });
};
<commit_msg>some fixes with user queries<commit_after>var MathLib = artifacts.require("./libraries/MathLib.sol");
var OrderLib = artifacts.require("./libraries/OrderLib.sol");
var CollateralToken = artifacts.require("./tokens/CollateralToken.sol");
var MarketContract = artifacts.require("./MarketContract.sol");
var MarketContractRegistry = artifacts.require("./MarketContractRegistry.sol");

// example of deploying a contract using a basic ERC20 token example as collateral
// and uses kraken's ETH/BTC price repeating  the queries every 30 seconds
module.exports = function(deployer) {
    deployer.deploy(MathLib);
    deployer.deploy(OrderLib);
    deployer.deploy(MarketContractRegistry)

    deployer.link(MathLib, MarketContract);
    deployer.link(OrderLib, MarketContract);

    deployer.deploy(CollateralToken).then(function() {
    return deployer.deploy(
                        MarketContract,
                        "ETHXBT",
                        CollateralToken.address,
                        "URL",
                        "json(https://api.kraken.com/0/public/Ticker?pair=ETHUSD).result.XETHZUSD.c.0",
                        30,
                        20155,
                        60465,
                        2,
                        10,
                        60 * 15,
                        {gas:6700000 ,  value: web3.toWei('.2', 'ether'), from: web3.eth.accounts[0]})
    })

    // add deployed contract to whitelist.
    deployer.then(function() {
        return MarketContractRegistry.deployed();
    }).then(function (instance) {
        instance.addAddressToWhiteList(MarketContract.address);
    });
};
<|endoftext|>


<commit_before>var formfiller = new FormFiller();
formfiller.loadForm();

/**
 * FormFiller
 *
 * Main FormFiller class, which handles all requests
 * @returns {FormFiller}
 */
function FormFiller() {

    var _version = '0.1.6';
    var _context = this;
    this.jsCode = 'javascript:/* FormFiller v'+_version+' */var d=document, e=new Event(\'change\');function i(a){return d.getElementById(a)}function n(a){return d.getElementsByName(a)}';

    this.loadForm = function () {
        _loadJQuery();
    };

    var _loadJQuery = function () {
        if (typeof jQuery === 'undefined') {
            new Loader().script('//code.jquery.com/jquery-1.11.1.min.js', function(context){context.doLoadForm();}, _context);
        } else {
            _context.doLoadForm();
        }
    };

    this.doLoadForm = function () {
        if(jQuery('div#formfiller').length === 0){
            var html = '

Form Filler v'+_version+'

<input id="formfiller-bookmarkletname" style="margin-bottom:18px" value="'+document.title+'"> <input type="button" value="Save" onclick="javascript:formfiller.save();return false;">

 Click and drag this link to the bookmarks bar: My bookmarklet
×

';
            jQuery('body').append(html);
        }
    };

    this.save = function() {
        jQuery('form input:not(:hidden,:radio,:checkbox,:submit,:file), form textarea, form select, form input[type="radio"]:checked, form input[type="checkbox"]:checked').each(function(){
            if(_hasName(this)){
                if(_isRadioOrCheckbox(this)){
                    formfiller.jsCode += 'i("'+_getId(this)+'").checked=true;i("'+_getId(this)+'").dispatchEvent(e);';
                } else {
                    formfiller.jsCode += 'n("'+_getName(this)+'")[0].value="'+jQuery(this).val().replace(/"/g, '\\"')+'";n("'+_getName(this)+'")[0].dispatchEvent(e);';
                }
            }
        });
        jQuery('#bookmarklet').attr('href', this.jsCode + 'void(0);').html(jQuery('#formfiller-bookmarkletname').val()).parent('p').show();
        jQuery('#formfiller-formwrapper').hide();
    };

    var _hasName = function(element){
        return (_getName(element) !== undefined);
    };

    var _getName = function(element){
        return jQuery(element).attr('name');
    };

    var _isRadioOrCheckbox = function(element){
        return ((_nameHasArray(element)) || (_isType(element, ['radio', 'checkbox'])))
    };

    var _nameHasArray = function(element){
        return (_getName(element).indexOf('[]') >= 0)
    };

    var _isType = function(element, types){
        return (jQuery.inArray(jQuery(element).attr('type'), types) >= 0);
    };

    var _getId = function(element){
        return jQuery(element).attr('id');
    };

}

/**
 * Loader
 *
 * Class for loading external files
 * Supported tags:
 *   - script
 *   - link
 * @returns {Loader}
 */
function Loader() {

    var TYPE = {
        SCRIPT: 'script'
    };

    var _fileLoaded = false;
    var _callback = null;
    var _context = null;

    this.script = function (url, callback, context) {
        _callback = callback;
        _context = context;
        _appendNewElementToBody(
            TYPE.SCRIPT
            , {
                src: url
                , type: 'text/javascript'
            }
        );
        _cleanup();
    };

    var _appendNewElementToBody = function (type, attrs) {
        _getFirstElementByTagName('body').appendChild(_createElement(type, attrs));
    };

    var _getFirstElementByTagName = function (name) {
        return document.getElementsByTagName(name)[0];
    };

    var _createElement = function (type, attrs) {
        var element = document.createElement(type);

        for (var key in attrs) {
            element.setAttribute(key, attrs[key]);
        }

        element.onload = element.onreadystatechage = function () {
            if (!_fileLoaded && (!this.readyState || ['loaded', 'complete'].indexOf(this.readyState) !== -1)) {
                _fileLoaded = true;
                element.onload = element.onreadystatechange = null;
                _getFirstElementByTagName('body').removeChild(element);
                _callback(_context);
            }
        };

        return element;
    };

    var _cleanup = function () {
        _fileLoaded = false;
    };

}
<commit_msg>Make FormFiller prefer ids over names #5<commit_after>var formfiller = new FormFiller();
formfiller.loadForm();

/**
 * FormFiller
 *
 * Main FormFiller class, which handles all requests
 * @returns {FormFiller}
 */
function FormFiller() {

    var _version = '0.1.7';
    var _context = this;
    this.jsCode = 'javascript:/* FormFiller v'+_version+' */var d=document, e=new Event(\'change\'),z;function i(a){return d.getElementById(a)}function n(a){return d.getElementsByName(a)}';

    this.loadForm = function () {
        _loadJQuery();
    };

    var _loadJQuery = function () {
        if (typeof jQuery === 'undefined') {
            new Loader().script('//code.jquery.com/jquery-1.11.1.min.js', function(context){context.doLoadForm();}, _context);
        } else {
            _context.doLoadForm();
        }
    };

    this.doLoadForm = function () {
        if(jQuery('div#formfiller').length === 0){
            var html = '

Form Filler v'+_version+'

<input id="formfiller-bookmarkletname" style="margin-bottom:18px" value="'+document.title+'"> <input type="button" value="Save" onclick="javascript:formfiller.save();return false;">

 Click and drag this link to the bookmarks bar: My bookmarklet
×

';
            jQuery('body').append(html);
        }
    };

    this.save = function() {
        jQuery('form input:not(:hidden,:radio,:checkbox,:submit,:file), form textarea, form select, form input[type="radio"]:checked, form input[type="checkbox"]:checked').each(function(){
            if(_hasName(this) || _hasId(this)){
                if(_hasName(this) && _hasId(this) && _isRadioOrCheckbox(this)){
                    formfiller.jsCode += 'z=i("'+_getId(this)+'");z.checked=true;';
                } else if(_hasId(this)){
                    formfiller.jsCode += 'z=i("'+_getId(this)+'");z.value="'+_getValue(this)+'";'
                } else {
                    formfiller.jsCode += 'z=n("'+_getName(this)+'")[0];z.value="'+_getValue(this)+'";';
                }
                formfiller.jsCode += 'z.dispatchEvent(e);';
            }
        });
        jQuery('#bookmarklet').attr('href', this.jsCode + 'void(0);').html(jQuery('#formfiller-bookmarkletname').val()).parent('p').show();
        jQuery('#formfiller-formwrapper').hide();
    };

    var _hasName = function(element){
        return (_getName(element) !== undefined);
    };

    var _getName = function(element){
        return jQuery(element).attr('name');
    };

    var _hasId = function(element){
        return (_getId(element) !== undefined);
    };

    var _getId = function(element){
        return jQuery(element).attr('id');
    };

    var _isRadioOrCheckbox = function(element){
        return ((_nameHasArray(element)) || (_isType(element, ['radio', 'checkbox'])))
    };

    var _nameHasArray = function(element){
        return (_getName(element).indexOf('[]') >= 0)
    };

    var _isType = function(element, types){
        return (jQuery.inArray(jQuery(element).attr('type'), types) >= 0);
    };

    var _getValue = function(element){
        return jQuery(element).val().replace(/"/g, '\\"');
    };

}

/**
 * Loader
 *
 * Class for loading external files
 * Supported tags:
 *   - script
 *   - link
 * @returns {Loader}
 */
function Loader() {

    var TYPE = {
        SCRIPT: 'script'
    };

    var _fileLoaded = false;
    var _callback = null;
    var _context = null;

    this.script = function (url, callback, context) {
        _callback = callback;
        _context = context;
        _appendNewElementToBody(
            TYPE.SCRIPT
            , {
                src: url
                , type: 'text/javascript'
            }
        );
        _cleanup();
    };

    var _appendNewElementToBody = function (type, attrs) {
        _getFirstElementByTagName('body').appendChild(_createElement(type, attrs));
    };

    var _getFirstElementByTagName = function (name) {
        return document.getElementsByTagName(name)[0];
    };

    var _createElement = function (type, attrs) {
        var element = document.createElement(type);

        for (var key in attrs) {
            element.setAttribute(key, attrs[key]);
        }

        element.onload = element.onreadystatechage = function () {
            if (!_fileLoaded && (!this.readyState || ['loaded', 'complete'].indexOf(this.readyState) !== -1)) {
                _fileLoaded = true;
                element.onload = element.onreadystatechange = null;
                _getFirstElementByTagName('body').removeChild(element);
                _callback(_context);
            }
        };

        return element;
    };

    var _cleanup = function () {
        _fileLoaded = false;
    };

}
<|endoftext|>