<commit_before>var Regularity = require('../lib/regularity.js');

describe("Regularity", function() {
    var regularity;

    beforeEach(function() {
        regularity = new Regularity();
    });

    it("is an object constructor", function() {
        expect(typeof Regularity).toBe('function');
        expect(typeof regularity).toBe('object');
    });

    describe("escapes regexp special characters", function() {
        var charactersToBeEscaped = ['*', '.', '?', '^', '+',
                                     '$', '|', '(', ')', '[',
                                     ']', '{', '}'];

        charactersToBeEscaped.forEach(function testEscapedChar(character) {
            it("escapes '" + character + "'", function() {
                var currentRegex = regularity.append(character).done();

                expect(currentRegex.source).toBe("\\" + character);
            });
        });
    });

    describe("#startWith requires that the passed pattern occur exactly at the beginning of the input", function() {
        var regex;
        beforeEach(function() {
            regex = regularity.startWith('a').done();
        });

        it("matches in the positive case", function() {
            expect(regex.test('abcde')).toBe(true);
        });

        it("does not match in the negative case", function() {
            expect(regex.test('edcba')).toBe(false);
        });

        it("can only be called once", function() {

        });

        it("can only be called as the first method in the chain", function() {

        });
    });

    describe("#startWith -- checks against literal regexp", function() {
        var regex;

        it("single character", function() {
            regex = regularity.startWith('a').done();
            expect(regex).toEqual(/^a/);
        });

        it("numbered pattern", function() {
            regex = regularity.startWith(4, 'digits').done();
            expect(regex).toEqual(/^[0-9]{4}/);
        });

        it("one character", function() {
            regex = regularity.startWith(1, 'p').done();
            expect(regex).toEqual(/^p{1}/);
        });

        it("more than one occurence of one character", function() {
            regex = regularity.startWith(6, 'p').done();
            expect(regex).toEqual(/^p{6}/);
        });

        xit("one occurence of several characters", function() {
            regex = regularity.startWith(1, 'hey').done();
            expect(regex).toEqual(/^(?:hey){1}/);
        });

        xit("more than one occurence of several characters", function() {
            regex = regularity.startWith(5, 'hey').done();
            expect(regex).toEqual(/^(?:hey){5}/);
        });
    });

    describe("#endWith requires that the passed pattern occur exactly at the end of the input", function() {
        var regex;
        beforeEach(function() {
            regex = regularity.endWith('a').done();
        });

        it("matches in the positive case", function() {
            expect(regex.test('edcba')).toBe(true);
        });

        it("does not match in the negative case", function() {
            expect(regex.test('abcde')).toBe(false);
        });

        it("can only be called once", function() {

        });

        it("can only be called as the last method in the chain (except for #done)", function() {

        });
    });

    describe("#maybe requires that the passed pattern occur either one or zero times", function() {
        var regex;
        beforeEach(function() {
            regex = regularity.maybe('a').done();
        });

        it("matches when the pattern is present", function() {
            expect(regex.test('aaaa')).toBe(true);
        });

        it("matches when the pattern isn't present", function() {
            expect(regex.test('bbbb')).toBe(true);
        });
    });

    describe("#oneOf requires that at least one of the passed patterns occur", function() {
        var regex;
        beforeEach(function() {
            regex = regularity.oneOf(['a','bb','ccc']).done();
        });

        it("matches the first one", function() {
            expect(regex.test('addd')).toBe(true);
        });

        it("matches the second one", function() {
            expect(regex.test('dbb')).toBe(true);
        });

        it("matches the third one", function() {
            expect(regex.test('zkcccl')).toBe(true);
        });

        it("does not match when neither are present", function() {
            expect(regex.test('bccddd')).toBe(true);
        });
    });

    describe("#between requires that the passed pattern occur a number of consecutive times within the specified interval", function() {
        var regex;
        beforeEach(function() {
            regex = regularity.between([3, 5], 'a').done();
        });

        it("matches when there is the right amount of consecutive occurences", function() {
            expect(regex.test('aaadd')).toBe(true);
            expect(regex.test('llaaaa')).toBe(true);
            expect(regex.test('lmaaaaakl')).toBe(true);
        });

        it("doesn't match when the count is less than the lower bound", function() {
            expect(regex.test('addd')).toBe(false);
            expect(regex.test('aadkb')).toBe(false);
        });

        // see https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Using_special_characters
        it("*does* match when the count is more than the upper bound", function() {
            expect(regex.test('daaaaaalk')).toBe(true);
            expect(regex.test('dmaaaaaaaaaalm')).toBe(true);
        });

        it("doesn't match when there are enough occurences but they are not consecutive", function() {
            expect(regex.test('azaazza')).toBe(false);
            expect(regex.test('zkalaamaa')).toBe(false);
            expect(regex.test('azakalaamaama')).toBe(false);
        });

        it("throws a native error when the lower bound is greater than the upper bound", function() {
        });
    });

    describe("#append requires that the passed pattern occur after what has been declared so far (and before whatever is declared afterwards)", function() {

    });

    it("#then is just an alias for #append", function() {

    });

    describe("#zeroOrMore requires that the passed pattern occur any number of consecutive times, including zero", function() {

    });

    describe("#oneOrMore requires that the passed pattern occur consecutively at least one time", function() {

    });

    describe("#atLeast requires that the passed pattern occur consecutively at least the specified number of times", function() {

    });

    describe("#atMost requires that the passed pattern occur consecutively at most the specified number of times", function() {

    });

    describe("#insensitive specifies that the search must be done case-insensitively", function() {

    });

    describe("#global specifies that the search must be performed as many times as necessary to identify all matches", function() {

    });

    describe("#multiline specifies that the input must be treated as multiple lines", function() {

    });

    describe("#done", function() {
        it("returns a RegExp instance", function() {
            expect(regularity.done() instanceof RegExp).toBe(true);
        });

        it ("returns an empty regexp by default", function() {
            expect(regularity.done()).toEqual(new RegExp());
        });
    });

    it("#regexp is just an alias for #done", function() {
        expect(regularity.regexp).toBe(regularity.done);
    });
});
<commit_msg>Rename 'regex' to 'regexp' for consistency.<commit_after>var Regularity = require('../lib/regularity.js');

describe("Regularity", function() {
    var regularity;

    beforeEach(function() {
        regularity = new Regularity();
    });

    it("is an object constructor", function() {
        expect(typeof Regularity).toBe('function');
        expect(typeof regularity).toBe('object');
    });

    describe("escapes regexp special characters", function() {
        var charactersToBeEscaped = ['*', '.', '?', '^', '+',
                                     '$', '|', '(', ')', '[',
                                     ']', '{', '}'];

        charactersToBeEscaped.forEach(function testEscapedChar(character) {
            it("escapes '" + character + "'", function() {
                var currentRegexp = regularity.append(character).done();

                expect(currentRegexp.source).toBe("\\" + character);
            });
        });
    });

    describe("#startWith requires that the passed pattern occur exactly at the beginning of the input", function() {
        var regexp;
        beforeEach(function() {
            regexp = regularity.startWith('a').done();
        });

        it("matches in the positive case", function() {
            expect(regexp.test('abcde')).toBe(true);
        });

        it("does not match in the negative case", function() {
            expect(regexp.test('edcba')).toBe(false);
        });

        it("can only be called once", function() {

        });

        it("can only be called as the first method in the chain", function() {

        });
    });

    describe("#startWith -- checks against literal regexp", function() {
        var regexp;

        it("single character", function() {
            regexp = regularity.startWith('a').done();
            expect(regexp).toEqual(/^a/);
        });

        it("numbered pattern", function() {
            regexp = regularity.startWith(4, 'digits').done();
            expect(regexp).toEqual(/^[0-9]{4}/);
        });

        it("one character", function() {
            regexp = regularity.startWith(1, 'p').done();
            expect(regexp).toEqual(/^p{1}/);
        });

        it("more than one occurence of one character", function() {
            regexp = regularity.startWith(6, 'p').done();
            expect(regexp).toEqual(/^p{6}/);
        });

        xit("one occurence of several characters", function() {
            regexp = regularity.startWith(1, 'hey').done();
            expect(regexp).toEqual(/^(?:hey){1}/);
        });

        xit("more than one occurence of several characters", function() {
            regexp = regularity.startWith(5, 'hey').done();
            expect(regexp).toEqual(/^(?:hey){5}/);
        });
    });

    describe("#endWith requires that the passed pattern occur exactly at the end of the input", function() {
        var regexp;
        beforeEach(function() {
            regexp = regularity.endWith('a').done();
        });

        it("matches in the positive case", function() {
            expect(regexp.test('edcba')).toBe(true);
        });

        it("does not match in the negative case", function() {
            expect(regexp.test('abcde')).toBe(false);
        });

        it("can only be called once", function() {

        });

        it("can only be called as the last method in the chain (except for #done)", function() {

        });
    });

    describe("#maybe requires that the passed pattern occur either one or zero times", function() {
        var regexp;
        beforeEach(function() {
            regexp = regularity.maybe('a').done();
        });

        it("matches when the pattern is present", function() {
            expect(regexp.test('aaaa')).toBe(true);
        });

        it("matches when the pattern isn't present", function() {
            expect(regexp.test('bbbb')).toBe(true);
        });
    });

    describe("#oneOf requires that at least one of the passed patterns occur", function() {
        var regexp;
        beforeEach(function() {
            regexp = regularity.oneOf(['a','bb','ccc']).done();
        });

        it("matches the first one", function() {
            expect(regexp.test('addd')).toBe(true);
        });

        it("matches the second one", function() {
            expect(regexp.test('dbb')).toBe(true);
        });

        it("matches the third one", function() {
            expect(regexp.test('zkcccl')).toBe(true);
        });

        it("does not match when neither are present", function() {
            expect(regexp.test('bccddd')).toBe(true);
        });
    });

    describe("#between requires that the passed pattern occur a number of consecutive times within the specified interval", function() {
        var regexp;
        beforeEach(function() {
            regexp = regularity.between([3, 5], 'a').done();
        });

        it("matches when there is the right amount of consecutive occurences", function() {
            expect(regexp.test('aaadd')).toBe(true);
            expect(regexp.test('llaaaa')).toBe(true);
            expect(regexp.test('lmaaaaakl')).toBe(true);
        });

        it("doesn't match when the count is less than the lower bound", function() {
            expect(regexp.test('addd')).toBe(false);
            expect(regexp.test('aadkb')).toBe(false);
        });

        // see https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Using_special_characters
        it("*does* match when the count is more than the upper bound", function() {
            expect(regexp.test('daaaaaalk')).toBe(true);
            expect(regexp.test('dmaaaaaaaaaalm')).toBe(true);
        });

        it("doesn't match when there are enough occurences but they are not consecutive", function() {
            expect(regexp.test('azaazza')).toBe(false);
            expect(regexp.test('zkalaamaa')).toBe(false);
            expect(regexp.test('azakalaamaama')).toBe(false);
        });

        it("throws a native error when the lower bound is greater than the upper bound", function() {
        });
    });

    describe("#append requires that the passed pattern occur after what has been declared so far (and before whatever is declared afterwards)", function() {

    });

    it("#then is just an alias for #append", function() {

    });

    describe("#zeroOrMore requires that the passed pattern occur any number of consecutive times, including zero", function() {

    });

    describe("#oneOrMore requires that the passed pattern occur consecutively at least one time", function() {

    });

    describe("#atLeast requires that the passed pattern occur consecutively at least the specified number of times", function() {

    });

    describe("#atMost requires that the passed pattern occur consecutively at most the specified number of times", function() {

    });

    describe("#insensitive specifies that the search must be done case-insensitively", function() {

    });

    describe("#global specifies that the search must be performed as many times as necessary to identify all matches", function() {

    });

    describe("#multiline specifies that the input must be treated as multiple lines", function() {

    });

    describe("#done", function() {
        it("returns a RegExp instance", function() {
            expect(regularity.done() instanceof RegExp).toBe(true);
        });

        it ("returns an empty regexp by default", function() {
            expect(regularity.done()).toEqual(new RegExp());
        });
    });

    it("#regexp is just an alias for #done", function() {
        expect(regularity.regexp).toBe(regularity.done);
    });
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const React = require('react');

const {NativeModules, StyleSheet, UIManager, View} = require('react-native');

const {TestModule} = NativeModules;

import type {SyntheticEvent} from 'react-native/Libraries/Types/CoreEventTypes';
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';

// Verify that RCTSnapshot is part of the UIManager since it is only loaded
// if you have linked against RCTTest like in tests, otherwise we will have
// a warning printed out
const RCTSnapshot = UIManager.getViewManagerConfig('RCTSnapshot')
  ? require('../../../RCTTest/RCTSnapshotNativeComponent')
  : View;

type SnapshotReadyEvent = SyntheticEvent<
  $ReadOnly<{testIdentifier: string, ...}>,
>;

type Props = $ReadOnly<{|
  ...ViewProps,
  onSnapshotReady?: ?(event: SnapshotReadyEvent) => mixed,
  testIdentifier?: ?string,
|}>;

class SnapshotViewIOS extends React.Component<Props> {
  onDefaultAction: (event: SnapshotReadyEvent) => void = (
    event: SnapshotReadyEvent,
  ) => {
    TestModule.verifySnapshot(TestModule.markTestPassed);
  };

  render(): React.Node {
    const testIdentifier = this.props.testIdentifier || 'test';
    const onSnapshotReady = this.props.onSnapshotReady || this.onDefaultAction;
    return (
      // $FlowFixMe - Typing ReactNativeComponent revealed errors
      <RCTSnapshot
        style={style.snapshot}
        {...this.props}
        onSnapshotReady={onSnapshotReady}
        testIdentifier={testIdentifier}
      />
    );
  }
}

const style = StyleSheet.create({
  snapshot: {
    flex: 1,
  },
});

module.exports = SnapshotViewIOS;
<commit_msg>Migrate UIManager.getViewManagerConfig -> UIManager.hasViewManagerConfig in SnapshotViewIOS<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const React = require('react');

const {NativeModules, StyleSheet, UIManager, View} = require('react-native');

const {TestModule} = NativeModules;

import type {SyntheticEvent} from 'react-native/Libraries/Types/CoreEventTypes';
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';

// Verify that RCTSnapshot is part of the UIManager since it is only loaded
// if you have linked against RCTTest like in tests, otherwise we will have
// a warning printed out
const RCTSnapshot = UIManager.hasViewManagerConfig('RCTSnapshot')
  ? require('../../../RCTTest/RCTSnapshotNativeComponent')
  : View;

type SnapshotReadyEvent = SyntheticEvent<
  $ReadOnly<{testIdentifier: string, ...}>,
>;

type Props = $ReadOnly<{|
  ...ViewProps,
  onSnapshotReady?: ?(event: SnapshotReadyEvent) => mixed,
  testIdentifier?: ?string,
|}>;

class SnapshotViewIOS extends React.Component<Props> {
  onDefaultAction: (event: SnapshotReadyEvent) => void = (
    event: SnapshotReadyEvent,
  ) => {
    TestModule.verifySnapshot(TestModule.markTestPassed);
  };

  render(): React.Node {
    const testIdentifier = this.props.testIdentifier || 'test';
    const onSnapshotReady = this.props.onSnapshotReady || this.onDefaultAction;
    return (
      // $FlowFixMe - Typing ReactNativeComponent revealed errors
      <RCTSnapshot
        style={style.snapshot}
        {...this.props}
        onSnapshotReady={onSnapshotReady}
        testIdentifier={testIdentifier}
      />
    );
  }
}

const style = StyleSheet.create({
  snapshot: {
    flex: 1,
  },
});

module.exports = SnapshotViewIOS;
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */

// Public node modules.
const _ = require('lodash');
const request = require('request');

// Purest strategies.<
const Purest = require('purest');

/**
 * Connect thanks to a third-party provider.
 *
 *
 * @param {String}    provider
 * @param {String}    access_token
 *
 * @return  {*}
 */

exports.connect = (provider, query) => {
  const access_token = query.access_token || query.code || query.oauth_token;

  return new Promise((resolve, reject) => {
    if (!access_token) {
      return reject(null, {
        message: 'No access_token.'
      });
    }

    // Get the profile.
    getProfile(provider, query, async (err, profile) => {
      if (err) {
        return reject(err);
      }

      // We need at least the mail.
      if (!profile.email) {
        return reject([{
          message: 'Email was not available.'
        }, null]);
      }

      try {
        const users = await strapi.plugins['users-permissions'].queries('user', 'users-permissions').find({
          email: profile.email
        });

        const advanced = await strapi.store({
          environment: '',
          type: 'plugin',
          name: 'users-permissions',
          key: 'advanced'
        }).get();

        if (_.isEmpty(_.find(users, {provider})) && !advanced.allow_register) {
          return resolve([null, [{ messages: [{ id: 'Auth.advanced.allow_register' }] }], 'Register action is actualy not available.']);
        }

        const user = _.find(users, {provider});

        if (!_.isEmpty(user)) {
          return resolve([user, null]);
        }

        if (!_.isEmpty(_.find(users, user => user.provider !== provider)) && advanced.unique_email) {
          return resolve([null, [{ messages: [{ id: 'Auth.form.error.email.taken' }] }], 'Email is already taken.']);
        }

        // Retrieve default role.
        const defaultRole = await strapi.plugins['users-permissions'].queries('role', 'users-permissions').findOne({ type: advanced.default_role }, []);

        // Create the new user.
        const params = _.assign(profile, {
          provider: provider,
          role: defaultRole._id || defaultRole.id
        });

        const createdUser = await strapi.plugins['users-permissions'].queries('user', 'users-permissions').create(params);

        return resolve([createdUser, null]);
      } catch (err) {
        reject([null, err]);
      }
    });
  });
};

/**
 * Helper to get profiles
 *
 * @param {String}   provider
 * @param {Function} callback
 */

const getProfile = async (provider, query, callback) => {
  const access_token = query.access_token || query.code || query.oauth_token;

  const grant = await strapi.store({
    environment: '',
    type: 'plugin',
    name: 'users-permissions',
    key: 'grant'
  }).get();

  switch (provider) {
    case 'discord': {
      const discord = new Purest({
        provider: 'discord',
        config: {
          'discord': {
            'https://discordapp.com/api/': {
              '__domain': {
                'auth': {
                  'auth': {'bearer': '[0]'}
                }
              },
              '{endpoint}': {
                '__path': {
                  'alias': '__default'
                }
              }
            }
          }
        }
      });
      discord.query().get('users/@me').auth(access_token).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          // Combine username and discriminator because discord username is not unique
          var username = `${body.username}#${body.discriminator}`;
          callback(null, {
            username: username,
            email: body.email
          });
        }
      });
      break;
    }
    case 'facebook': {
      const facebook = new Purest({
        provider: 'facebook'
      });

      facebook.query().get('me?fields=name,email').auth(access_token).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.name,
            email: body.email
          });
        }
      });
      break;
    }
    case 'google': {
      const google = new Purest({
        provider: 'google'
      });

      google.query('plus').get('people/me').auth(access_token).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.emails[0].value.split("@")[0],
            email: body.emails[0].value
          });
        }
      });
      break;
    }
    case 'github': {
      const github = new Purest({
        provider: 'github',
        defaults: {
          headers: {
            'user-agent': 'strapi'
          }
        }
      });

      request.post({
        url: 'https://github.com/login/oauth/access_token',
        form: {
          client_id: grant.github.key,
          client_secret: grant.github.secret,
          code: access_token
        }
      }, (err, res, body) => {
        github.query().get('user').auth(body.split('&')[0].split('=')[1]).request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: body.login,
              email: body.email
            });
          }
        });
      });
      break;
    }
    case 'microsoft': {
      const microsoft = new Purest({
        provider: 'microsoft',
        config:{
          'microsoft': {
            'https://graph.microsoft.com': {
              '__domain': {
                'auth': {
                  'auth': {'bearer': '[0]'}
                }
              },
              '[version]/{endpoint}': {
                '__path': {
                  'alias': '__default',
                  'version': 'v1.0'
                }
              }
            }
          }
        }
      });

      microsoft.query().get('me').auth(access_token).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.userPrincipalName,
            email: body.userPrincipalName
          });
        }
      });
      break;
    }
    case 'twitter': {
      const twitter = new Purest({
        provider: 'twitter',
        key: grant.twitter.key,
        secret: grant.twitter.secret
      });

      twitter.query().get('account/verify_credentials').auth(access_token, query.access_secret).qs({screen_name: query['raw[screen_name]'], include_email: 'true'}).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.screen_name,
            email: body.email
          });
        }
      });
      break;
    }
    default:
      callback({
        message: 'Unknown provider.'
      });
      break;
  }
};
<commit_msg>Fix Google auth<commit_after>'use strict';

/**
 * Module dependencies.
 */

// Public node modules.
const _ = require('lodash');
const request = require('request');

// Purest strategies.<
const Purest = require('purest');

/**
 * Connect thanks to a third-party provider.
 *
 *
 * @param {String}    provider
 * @param {String}    access_token
 *
 * @return  {*}
 */

exports.connect = (provider, query) => {
  const access_token = query.access_token || query.code || query.oauth_token;

  return new Promise((resolve, reject) => {
    if (!access_token) {
      return reject(null, {
        message: 'No access_token.'
      });
    }

    // Get the profile.
    getProfile(provider, query, async (err, profile) => {
      if (err) {
        return reject(err);
      }

      // We need at least the mail.
      if (!profile.email) {
        return reject([{
          message: 'Email was not available.'
        }, null]);
      }

      try {
        const users = await strapi.plugins['users-permissions'].queries('user', 'users-permissions').find({
          email: profile.email
        });

        const advanced = await strapi.store({
          environment: '',
          type: 'plugin',
          name: 'users-permissions',
          key: 'advanced'
        }).get();

        if (_.isEmpty(_.find(users, {provider})) && !advanced.allow_register) {
          return resolve([null, [{ messages: [{ id: 'Auth.advanced.allow_register' }] }], 'Register action is actualy not available.']);
        }

        const user = _.find(users, {provider});

        if (!_.isEmpty(user)) {
          return resolve([user, null]);
        }

        if (!_.isEmpty(_.find(users, user => user.provider !== provider)) && advanced.unique_email) {
          return resolve([null, [{ messages: [{ id: 'Auth.form.error.email.taken' }] }], 'Email is already taken.']);
        }

        // Retrieve default role.
        const defaultRole = await strapi.plugins['users-permissions'].queries('role', 'users-permissions').findOne({ type: advanced.default_role }, []);

        // Create the new user.
        const params = _.assign(profile, {
          provider: provider,
          role: defaultRole._id || defaultRole.id
        });

        const createdUser = await strapi.plugins['users-permissions'].queries('user', 'users-permissions').create(params);

        return resolve([createdUser, null]);
      } catch (err) {
        reject([null, err]);
      }
    });
  });
};

/**
 * Helper to get profiles
 *
 * @param {String}   provider
 * @param {Function} callback
 */

const getProfile = async (provider, query, callback) => {
  const access_token = query.access_token || query.code || query.oauth_token;

  const grant = await strapi.store({
    environment: '',
    type: 'plugin',
    name: 'users-permissions',
    key: 'grant'
  }).get();

  switch (provider) {
    case 'discord': {
      const discord = new Purest({
        provider: 'discord',
        config: {
          'discord': {
            'https://discordapp.com/api/': {
              '__domain': {
                'auth': {
                  'auth': {'bearer': '[0]'}
                }
              },
              '{endpoint}': {
                '__path': {
                  'alias': '__default'
                }
              }
            }
          }
        }
      });
      discord.query().get('users/@me').auth(access_token).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          // Combine username and discriminator because discord username is not unique
          var username = `${body.username}#${body.discriminator}`;
          callback(null, {
            username: username,
            email: body.email
          });
        }
      });
      break;
    }
    case 'facebook': {
      const facebook = new Purest({
        provider: 'facebook'
      });

      facebook.query().get('me?fields=name,email').auth(access_token).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.name,
            email: body.email
          });
        }
      });
      break;
    }
    case 'google': {
      const config = {
        "google": {
          "https://www.googleapis.com": {
            "__domain": {
              "auth": {
                "auth": {"bearer": "[0]"}
              }
            },
            "{endpoint}": {
              "__path": {
                "alias": "__default"
              }
            },
            "oauth/[version]/{endpoint}": {
              "__path": {
                "alias": "oauth",
                "version": "v3"
              }
            }
          }
        }
      }
      var google = new Purest({provider: 'google', config})

      google.query('oauth').get('tokeninfo').qs({access_token}).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.email.split("@")[0],
            email: body.email
          });
        }
      });
      break;
    }
    case 'github': {
      const github = new Purest({
        provider: 'github',
        defaults: {
          headers: {
            'user-agent': 'strapi'
          }
        }
      });

      request.post({
        url: 'https://github.com/login/oauth/access_token',
        form: {
          client_id: grant.github.key,
          client_secret: grant.github.secret,
          code: access_token
        }
      }, (err, res, body) => {
        github.query().get('user').auth(body.split('&')[0].split('=')[1]).request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: body.login,
              email: body.email
            });
          }
        });
      });
      break;
    }
    case 'microsoft': {
      const microsoft = new Purest({
        provider: 'microsoft',
        config:{
          'microsoft': {
            'https://graph.microsoft.com': {
              '__domain': {
                'auth': {
                  'auth': {'bearer': '[0]'}
                }
              },
              '[version]/{endpoint}': {
                '__path': {
                  'alias': '__default',
                  'version': 'v1.0'
                }
              }
            }
          }
        }
      });

      microsoft.query().get('me').auth(access_token).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.userPrincipalName,
            email: body.userPrincipalName
          });
        }
      });
      break;
    }
    case 'twitter': {
      const twitter = new Purest({
        provider: 'twitter',
        key: grant.twitter.key,
        secret: grant.twitter.secret
      });

      twitter.query().get('account/verify_credentials').auth(access_token, query.access_secret).qs({screen_name: query['raw[screen_name]'], include_email: 'true'}).request((err, res, body) => {
        if (err) {
          callback(err);
        } else {
          callback(null, {
            username: body.screen_name,
            email: body.email
          });
        }
      });
      break;
    }
    default:
      callback({
        message: 'Unknown provider.'
      });
      break;
  }
};
<|endoftext|>


<commit_before>import { get, getAll, urlFor } from '_common/language';

export const currentLanguage = get();

export const getAllowedLanguages = () => {
    const exclude_languages = [
        'ACH',
    ];

    // const language_list = Object.keys(getAll())
    //     .filter(key => !(exclude_languages.includes(key)))
    //     .reduce((obj, key) => {
    //         obj[key] = getAll()[key];
    //         return obj;
    //     }, {});

    // TODO uncomment language_list and replace ['en'] when design is ready.
    return ['en'];
};

export const getURL = lang => urlFor(lang);
<commit_msg>much simpler linterfix<commit_after>import { get, getAll, urlFor } from '_common/language';

export const currentLanguage = get();

export const getAllowedLanguages = () => {
    const exclude_languages = [
        'ACH',
    ];
    // TODO Change language_list to const when design is ready.
    let language_list = Object.keys(getAll())
        .filter(key => !(exclude_languages.includes(key)))
        .reduce((obj, key) => {
            obj[key] = getAll()[key];
            return obj;
        }, {});

    // TODO Remove this one line below when design is ready.
    language_list = ['en'];
    return language_list;
};

export const getURL = lang => urlFor(lang);
<|endoftext|>


<commit_before>ngapp.service('contextMenuFactory', function() {
    let divider = {
        visible: (scope, items) => { return items.length > 0 && !items.last().divider; },
        build: (scope, items) => items.push({ divider: true })
    };

    let testNodes = function(nodes, testFn) {
        return nodes.reduce(function (b, node) {
            return b && testFn(node);
        }, true);
    };

    this.mainTreeItems = [{
        id: 'Add',
        visible: (scope) => {
            let node = scope.selectedNodes.last();
            return node.element_type !== xelib.etMainRecord && xelib.GetCanAdd(node.handle);
        },
        build: (scope, items) => {
            let node = scope.selectedNodes.last(),
                addList = xelib.GetAddList(node.handle),
                isFile = node.element_type === xelib.etFile;
            items.push({
                label: `Add ${isFile ? 'Group' : 'Record'}`,
                hotkey: isFile ? 'Ctrl+G' : (addList.length == 1 && 'Ctrl+N'),
                disabled: !addList.length,
                children: addList.map(function(item) {
                    return {
                        label: item,
                        callback: () => scope.addElement(node, item)
                    };
                })
            });
        }
    }, {
        id: 'Delete',
        visible: (scope) => {
            return testNodes(scope.selectedNodes, function(node) {
                return xelib.GetIsRemoveable(node.handle);
            });
        },
        build: (scope, items) => {
            items.push({
                label: 'Delete',
                hotkey: 'Del',
                callback: () => scope.deleteElements()
            });
        }
    }, {
        id: 'Refactor',
        visible: (scope) => {
            let nodes = scope.selectedNodes, node = nodes.last();
            return node.element_type === xelib.etFile && xelib.GetIsEditable(node.handle) ||
                testNodes(nodes, function(node) {
                    return node.element_type === xelib.etMainRecord && xelib.GetIsEditable(node.handle);
                });
        },
        build: (scope, items) => {
            let node = scope.selectedNodes.last(),
                children = [];
            if (node.element_type === xelib.etFile) {
                children.push({
                    label: 'Rename File',
                    hotkey: 'F2, Alt+Shift+R',
                    callback: () => scope.changeFileName(node)
                }, {
                    label: 'Change Author',
                    hotkey: 'Alt+Shift+A',
                    callback: () => scope.changeFileAuthor(node)
                }, {
                    label: 'Change Description',
                    hotkey: 'Alt+Shift+D',
                    callback: () => scope.changeFileDescription(node)
                });
            } else if (node.element_type === xelib.etMainRecord) {
                children.push({
                    label: 'Rename',
                    hotkey: 'F2, Alt+Shift+R',
                    callback: () => scope.toggleRefactorModal(true, 'Name')
                }, {
                    label: 'Change EditorIDs',
                    hotkey: 'Alt+Shift+E',
                    callback: () => scope.toggleRefactorModal(true, 'EditorID')
                }, {
                    label: 'Change FormIDs',
                    hotkey: 'Alt+Shift+F',
                    callback: () => scope.toggleRefactorModal(true, 'FormID')
                });
            }
            items.push({
                label: 'Refactor',
                children: children
            });
        }
    }, {
        id: 'Enable Editing',
        visible: (scope) => {
            return testNodes(scope.selectedNodes, function(node) {
                return node.element_type === xelib.etFile && !xelib.GetIsEditable(node.handle);
            });
        },
        build: (scope, items) => {
            items.push({
                label: 'Enable Editing',
                hotkey: 'Ctrl+E',
                callback: () => scope.enableEditing(node)
            })
        }
    }, divider, {
        id: 'Automate',
        visible: () => { return true; },
        build: (scope, items) => {
            items.push({
                label: 'Automate',
                hotkey: 'Ctrl+M',
                callback: () => scope.toggleAutomateModal()
            })
        }
    }, {
        id: 'Advanced Search',
        visible: () => { return true; },
        build: (scope, items) => {
            items.push({
                label: 'Advanced Search',
                hotkey: 'Ctrl+Shift+F',
                callback: () => scope.toggleSearchModal()
            })
        }
    }, {
        id: 'Open',
        visible: (scope) => {
            return testNodes(scope.selectedNodes, function(node) {
                return node.element_type === xelib.etMainRecord;
            });
        },
        build: (scope, items) => {
            items.push({
                label: 'Open in Record View',
                hotkey: 'Enter',
                callback: () => scope.onNodeDoubleClick({}, node)
            })
        }
    }, divider, {
        id: 'Copy',
        visible: () => { return true; },
        build: (scope, items) => {
            let node = scope.selectedNodes.last();
            items.push({
                label: 'Copy',
                hotkey: 'Ctrl+C',
                disabled: !node,
                callback: () => scope.copyNodes()
            })
        }
    }, {
        id: 'Copy Path',
        visible: () => { return true; },
        build: (scope, items) => {
            let node = scope.selectedNodes.last();
            items.push({
                label: 'Copy Path',
                hotkey: 'Ctrl+Shift+C',
                disabled: !node,
                callback: () => scope.copyNodePaths()
            })
        }
    }, {
        id: 'Paste',
        visible: () => { return true },
        build: (scope, items) => {
            items.push({
                label: 'Paste',
                hotkey: 'Ctrl+V',
                disabled: !scope.canPaste(),
                callback: () => scope.paste()
            })
        }
    }, {
        id: 'Paste as Override',
        visible: () => { return true; },
        build: (scope, items) => {
            items.push({
                label: 'Paste as Override',
                hotkey: 'Ctrl+Shift+V',
                disabled: !scope.canPaste(true),
                callback: () => scope.paste(true)
            })
        }
    }];
});<commit_msg>added record tree node context menu items to context menu factory<commit_after>ngapp.service('contextMenuFactory', function() {
    let uneditableValueTypes = [xelib.vtUnknown, xelib.vtArray, xelib.vtStruct];

    let divider = {
        visible: (scope, items) => { return items.length > 0 && !items.last().divider; },
        build: (scope, items) => items.push({ divider: true })
    };

    let testNodes = function(nodes, testFn) {
        return nodes.reduce(function (b, node) {
            return b && testFn(node);
        }, true);
    };

    this.mainTreeItems = [{
        id: 'Add',
        visible: (scope) => {
            let node = scope.selectedNodes.last();
            return node.element_type !== xelib.etMainRecord && xelib.GetCanAdd(node.handle);
        },
        build: (scope, items) => {
            let node = scope.selectedNodes.last(),
                addList = xelib.GetAddList(node.handle),
                isFile = node.element_type === xelib.etFile;
            items.push({
                label: `Add ${isFile ? 'Group' : 'Record'}`,
                hotkey: isFile ? 'Ctrl+G' : (addList.length == 1 && 'Ctrl+N'),
                disabled: !addList.length,
                children: addList.map(function(item) {
                    return {
                        label: item,
                        callback: () => scope.addElement(node, item)
                    };
                })
            });
        }
    }, {
        id: 'Delete',
        visible: (scope) => {
            return testNodes(scope.selectedNodes, function(node) {
                return xelib.GetIsRemoveable(node.handle);
            });
        },
        build: (scope, items) => {
            items.push({
                label: 'Delete',
                hotkey: 'Del',
                callback: () => scope.deleteElements()
            });
        }
    }, {
        id: 'Refactor',
        visible: (scope) => {
            let nodes = scope.selectedNodes, node = nodes.last();
            return node.element_type === xelib.etFile && xelib.GetIsEditable(node.handle) ||
                testNodes(nodes, function(node) {
                    return node.element_type === xelib.etMainRecord && xelib.GetIsEditable(node.handle);
                });
        },
        build: (scope, items) => {
            let node = scope.selectedNodes.last(),
                children = [];
            if (node.element_type === xelib.etFile) {
                children.push({
                    label: 'Rename File',
                    hotkey: 'F2, Alt+Shift+R',
                    callback: () => scope.changeFileName(node)
                }, {
                    label: 'Change Author',
                    hotkey: 'Alt+Shift+A',
                    callback: () => scope.changeFileAuthor(node)
                }, {
                    label: 'Change Description',
                    hotkey: 'Alt+Shift+D',
                    callback: () => scope.changeFileDescription(node)
                });
            } else if (node.element_type === xelib.etMainRecord) {
                children.push({
                    label: 'Rename',
                    hotkey: 'F2, Alt+Shift+R',
                    callback: () => scope.toggleRefactorModal(true, 'Name')
                }, {
                    label: 'Change EditorIDs',
                    hotkey: 'Alt+Shift+E',
                    callback: () => scope.toggleRefactorModal(true, 'EditorID')
                }, {
                    label: 'Change FormIDs',
                    hotkey: 'Alt+Shift+F',
                    callback: () => scope.toggleRefactorModal(true, 'FormID')
                });
            }
            items.push({
                label: 'Refactor',
                children: children
            });
        }
    }, {
        id: 'Enable Editing',
        visible: (scope) => {
            return testNodes(scope.selectedNodes, function(node) {
                return node.element_type === xelib.etFile && !xelib.GetIsEditable(node.handle);
            });
        },
        build: (scope, items) => {
            items.push({
                label: 'Enable Editing',
                hotkey: 'Ctrl+E',
                callback: () => scope.enableEditing(node)
            })
        }
    }, divider, {
        id: 'Automate',
        visible: () => { return true; },
        build: (scope, items) => {
            items.push({
                label: 'Automate',
                hotkey: 'Ctrl+M',
                callback: () => scope.toggleAutomateModal()
            })
        }
    }, {
        id: 'Advanced Search',
        visible: () => { return true; },
        build: (scope, items) => {
            items.push({
                label: 'Advanced Search',
                hotkey: 'Ctrl+Shift+F',
                callback: () => scope.toggleSearchModal()
            })
        }
    }, {
        id: 'Open',
        visible: (scope) => {
            return testNodes(scope.selectedNodes, function(node) {
                return node.element_type === xelib.etMainRecord;
            });
        },
        build: (scope, items) => {
            items.push({
                label: 'Open in Record View',
                hotkey: 'Enter',
                callback: () => scope.onNodeDoubleClick({}, node)
            })
        }
    }, divider, {
        id: 'Copy',
        visible: () => { return true; },
        build: (scope, items) => {
            let node = scope.selectedNodes.last();
            items.push({
                label: 'Copy',
                hotkey: 'Ctrl+C',
                disabled: !node,
                callback: () => scope.copyNodes()
            })
        }
    }, {
        id: 'Copy Path',
        visible: () => { return true; },
        build: (scope, items) => {
            let node = scope.selectedNodes.last();
            items.push({
                label: 'Copy Path',
                hotkey: 'Ctrl+Shift+C',
                disabled: !node,
                callback: () => scope.copyNodePaths()
            })
        }
    }, {
        id: 'Paste',
        visible: () => { return true },
        build: (scope, items) => {
            items.push({
                label: 'Paste',
                hotkey: 'Ctrl+V',
                disabled: !scope.canPaste(),
                callback: () => scope.paste()
            })
        }
    }, {
        id: 'Paste as Override',
        visible: () => { return true; },
        build: (scope, items) => {
            items.push({
                label: 'Paste as Override',
                hotkey: 'Ctrl+Shift+V',
                disabled: !scope.canPaste(true),
                callback: () => scope.paste(true)
            })
        }
    }];

    this.recordTreeItems = [{
        id: 'Add',
        visible: (scope) => {
            let node = scope.selectedNodes.last(),
                index = scope.focusedIndex - 1,
                handle = node.handles[index],
                record = index ? scope.overrides[index - 1] : scope.record,
                parentAvailable = !node.parent || node.parent.handles[index];
            if (!xelib.GetIsEditable(record)) return false;
            return parentAvailable && (handle === 0 || node.value_type === xelib.vtArray);
        },
        build: (scope, items) => {
            let node = scope.selectedNodes.last(),
                index = scope.focusedIndex - 1;
            items.push({
                label: 'Add',
                hotkey: 'Insert',
                callback: () => scope.addElement(node, index)
            });
        }
    }, {
        id: 'Edit',
        visible: (scope) => {
            let node = scope.selectedNodes.last(),
                index = scope.focusedIndex - 1,
                handle = node.handles[index],
                record = index === 0 ? scope.record : scope.overrides[index - 1];
            if (!xelib.GetIsEditable(record)) return false;
            return handle !== 0 && !uneditableValueTypes.contains(node.value_type);
        },
        build: (scope, items) => {
            let node = scope.selectedNodes.last(),
                index = scope.focusedIndex - 1;
            items.push({
                label: 'Add',
                hotkey: 'Insert',
                callback: () => scope.editElement(node, index)
            });
        }
    }, {
        id: 'Delete',
        visible: (scope) => {
            let index = scope.focusedIndex - 1;
            return testNodes(scope.selectedNodes, function(node) {
                return xelib.GetIsRemoveable(node.handles[index]);
            });
        },
        build: (scope, items) => {
            items.push({
                label: 'Delete',
                hotkey: 'Del',
                callback: () => scope.deleteElements()
            });
        }
    }, divider, {
        id: 'Copy',
        visible: () => { return true; },
        build: (scope, items) => {
            let node = scope.selectedNodes.last();
            items.push({
                label: 'Copy',
                hotkey: 'Ctrl+C',
                disabled: !node,
                callback: () => scope.copyNodes()
            })
        }
    }, {
        id: 'Copy Path',
        visible: () => { return true; },
        build: (scope, items) => {
            let node = scope.selectedNodes.last();
            items.push({
                label: 'Copy Path',
                hotkey: 'Ctrl+Shift+C',
                disabled: !node,
                callback: () => scope.copyNodePaths()
            })
        }
    }, {
        id: 'Paste',
        visible: () => { return true },
        build: (scope, items) => {
            items.push({
                label: 'Paste',
                hotkey: 'Ctrl+V',
                disabled: !scope.canPaste(),
                callback: () => scope.paste()
            })
        }
    }]
});<|endoftext|>


<commit_before>import _ from 'lodash'
import Ember from 'ember'
import computed, {readOnly} from 'ember-computed-decorators'

import layout from '../templates/components/frost-select'

// TODO: Add typedefs for items

// TODO: add jsdoc
function isAttrDifferent (newAttrs, oldAttrs, attributeName) {
  let oldValue = _.get(oldAttrs, attributeName + '.value')
  let newValue = _.get(newAttrs, attributeName + '.value')

  if (newValue && !_.isEqual(oldValue, newValue)) {
    return true
  }
  return false
}

export default Ember.Component.extend({

  // ==========================================================================
  // Dependencies
  // ==========================================================================

  // ==========================================================================
  // Properties
  // ==========================================================================

  attributeBindings: ['tabIndex'],
  classNames: ['frost-select'],
  classNameBindings: ['focus', 'shouldOpen:open', 'disabled', 'hasError:error'],
  disabled: false,
  error: false,
  hovered: -1,
  filter: undefined,
  layout,
  maxListHeight: 400,
  tabIndex: -1,
  width: 200,

  // ==========================================================================
  // Computed Properties
  // ==========================================================================

  @readOnly
  @computed('items', 'selected', 'hovered', 'filter')
  /**
   * Get the display items
   * @param {Object[]} items - the possible items
   * @param {Number[]} selected - the array of selected indices
   * @param {Number} hovered - index currently being hovered over (or -1)
   * @param {String} filter - search filter
   * @returns {Object[]} the display items
   */
  displayItems (items, selected, hovered, filter) {
    const result = []
    const valid = this.getValid(filter)

    valid.forEach((validItem, index, list) => {
      let className = ''
      if (selected.indexOf(validItem.index) !== -1) {
        className += ' selected'
        validItem.selected = true
      }

      if (index === hovered || list.length === 1) {
        className += ' hover'
      }

      validItem.className = className
      result.push(validItem)
    })

    return result
  },

  @readOnly
  @computed('error', 'invalidFilter')
  /**
   * Computed flag for if consumer flagged us as having an error, or if the user has typed
   * something bad.
   * @param {Boolean} error - true if consumer has flagged that an error exists
   * @param {Boolean} invalidFilter - true if the user has typed something that doesn't match an option
   * @returns {Boolean} true if either error condition occured
   */
  hasError (error, invalidFilter) {
    return error || invalidFilter
  },

  @readOnly
  @computed('data', 'displayItems')
  /**
   * Flag for if the user has typed something that doesn't match any options
   * @param {Object[]} data - all the possible items
   * @param {Object[]} displayItems - the current items being displayed
   * @returns {Boolean} true if in error state
   */
  invalidFilter (data, displayItems) {
    return _.isArray(data) && (data.length > 0) && _.isArray(displayItems) && (displayItems.length === 0)
  },

  @readOnly
  @computed('data')
  /**
   * Create items from passed-in data
   * @param {Object[]} data - the possible value data passed in
   * @returns {Object[]} the items
   */
  items (data) {
    return data.map((item, index) => {
      return {
        label: item.label,
        value: item.value,
        index,
        classNames: ''
      }
    })
  },

  @readOnly
  @computed('selected')
  /**
   * Build the prompt based on the selected item(s)
   * @param {Number[]} selected - the selected indices
   * @returns {String} the prompt
   */
  prompt (selected) {
    const data = this.get('data')
    const filter = this.get('filter')
    let prompt = ''

    if (filter !== undefined) {
      prompt = filter
    } else {
      let selectedItem = data[selected[0]]
      if (selectedItem) {
        prompt = selectedItem.label
      }
    }
    return prompt
  },

  @readOnly
  @computed('invalidFilter', 'shouldDisableDropDown', 'open')
  /**
   * Determine if drop-down should open
   * @param {Boolean} invalidFilter - did the user goof?
   * @param {Boolean} shouldDisableDropDown - computed flag for whether opening the drop-down should be disabled
   * @param {Boolean} open - TODO: what is this?
   * @returns {Boolean} true if we should open
   */
  shouldOpen (invalidFilter, shouldDisableDropDown, open) {
    return !invalidFilter && !shouldDisableDropDown && open
  },

  @readOnly
  @computed('invalidFilter', 'disabled')
  /**
   * Determine if we should disable opening the drop-down
   * @param {Boolean} invalidFilter - did the user goof?
   * @param {Boolean} disabled - are we in a disabled state?
   * @returns {Boolean} true if opening should be disabled
   */
  shouldDisableDropDown (invalidFilter, disabled) {
    return invalidFilter || disabled
  },

  @readOnly
  @computed('width')
  /**
   * Compute the style attribute based on width
   * @param {Number} width - the width property
   * @returns {String} the computed style attribute
   */
  style (width) {
    return `width: ${width}px`
  },

  // ==========================================================================
  // Functions
  // ==========================================================================

  /* Ember.Component method */
  init () {
    this._super(...arguments)

    if (this.get('selected') === undefined) {
      this.set('selected', [])
    }
  },

  /* Ember.Component method */
  didReceiveAttrs (attrs) {
    this._super(...arguments)
    if (isAttrDifferent(attrs.newAttrs, attrs.oldAttrs, 'selectedValue')) {
      this.selectOptionByValue(attrs.newAttrs.selectedValue.value)
    } else if (isAttrDifferent(attrs.newAttrs, attrs.oldAttrs, 'selected')) {
      let selected = this.get('selected')

      selected = selected && (_.isArray(selected) || _.isNumber(selected)) ? [].concat(selected) : []
      this.set('selected', selected)
    }
  },

  /* Ember.didInsertElement method */
  didInsertElement () {
    this._super(...arguments)
    const outsideClickHandler = this.get('handleOutsideClick').bind(this)
    Ember.$(document).on('click', outsideClickHandler)
  },

  /* Ember.willDestroyElement method */
  willDestroyElement () {
    const outsideClickHandler = this.get('handleOutsideClick').bind(this)
    Ember.$(document).on('click', outsideClickHandler)
    this._super(...arguments)
  },

  // TODO: add jsdoc
  chooseHovered () {
    let displayItem = this.get('displayItems')[this.get('hovered')]
    this.select(displayItem.index)
  },

  // TODO: add jsdoc
  click (event) {
    // event.preventDefault()
  },

  // TODO: add jsdoc
  closeList () {
    this.setProperties({open: false, filter: undefined, hovered: -1})
    this.inputElement().val(this.get('prompt'))
  },

  // TODO: add jsdoc
  getLabel (item) {
    return item.label
  },

  // TODO: add jsdoc
  getValid (filter) {
    let valid = []
    this.get('items').forEach((item, index, list) => {
      if (!filter || this.getLabel(item).toLowerCase().indexOf(filter.toLowerCase()) !== -1) {
        valid.push({
          index,
          value: item.value,
          label: this.getLabel(item)
        })
      }
    })
    return valid
  },

  // TODO: add jsdoc
  getValues (selected = this.get('selected')) {
    return selected.map((selectedIndex) => {
      return this.get('data')[selectedIndex].value
    })
  },

  // TODO: add jsdoc
  hoverNext () {
    let hovered = this.get('hovered')
    if (hovered === this.get('displayItems').length - 1) {
      hovered = 0
    } else {
      hovered += 1
    }
    this.set('hovered', hovered)
  },

  // TODO: add jsdoc
  hoverPrev () {
    let hovered = this.get('hovered')
    if (hovered === 0) {
      hovered = this.get('displayItems').length - 1
    } else {
      hovered -= 1
    }
    this.set('hovered', hovered)
  },

  handleOutsideClick (event) {
    const $target = Ember.$(event.target)
    if (!$target.closest(this.$()).length) {
      this.onOutsideClick()
    }
  },

  // TODO: add jsdoc
  inputElement () {
    return this.$('input')
  },

  /** Key down event handler
   * @param {JQuery.Event} event - event object
   */
  keyDown (event) {
    // if tab is pushed - close list
    if (event.which === 9) {
      if (this.get('open')) {
        this.closeList()
      }
    }
  },

  // TODO: add jsdoc
  keyUp (event) {
    switch (event.which) {

      // escape key or tab key, close the dropdown
      case 27:
        event.stopPropagation()
        if (this.get('open')) {
          this.toggle(event)
        }
        break

      // enter + spacebar, choose selected
      case 13:
        this.chooseHovered()
        break

      // up arrow
      case 38:
        event.preventDefault()
        this.hoverPrev()
        break

      // down arrow, open the dropdown if necessary, select next
      case 40:
        event.preventDefault()
        if (!this.get('open')) {
          this.openList()
        }
        this.hoverNext()
        break

      // backspace
      case 8:
        event.preventDefault()
        if (!this.get('open')) {
          this.openList()
        }
    }
  },

  /**
   * Notify parent of currently selected values by calling the onChange callback
   * with the values of the currently selected indices
   * @param {Number[]} selected - the selected indices
   */
  notifyOfChange (selected) {
    const onChange = this.get('on-change')
    if (onChange) {
      const values = this.getValues(selected)
      onChange(values)
    }
  },

  /* obvious */
  openList () {
    this.set('open', true)
  },

  /** Handler for click outside of an element
   */
  onOutsideClick () {
    if (this.get('open')) {
      this.closeList()
    }
  },

  // TODO: add jsdoc
  search (term) {
    let valid = this.getValid(term)
    let newProps = {filter: term, hovered: -1}
    this.setProperties(newProps)
    if (valid.length === 1) {
      this.hoverNext()
    }
  },

  // TODO: add jsdoc
  select (index) {
    let selected = [index]
    let values = this.getValues(selected)
    this.setProperties({
      selected: selected,
      open: false
    })
    this.set('filter', undefined)
    if (this.get('on-change') && _.isFunction(this.get('on-change'))) {
      this.get('on-change')(values)
    }
  },

  // TODO: add jsdoc
  selectOptionByValue (selectedValue) {
    // Find index
    let valueIndex = _.findIndex(this.get('items'), (item) => _.isEqual(item.value, selectedValue))

    if (valueIndex >= 0) { // Make sure we actually found the value
      this.select(valueIndex)
    }
  },

  // TODO: add jsdoc
  toggle (event) {
    event.preventDefault()
    if (this.get('shouldDisableDropDown')) {
      return
    }

    if (this.get('open')) {
      this.closeList()
      return
    }
    this.openList()
  },

  // ==========================================================================
  // Events
  // ==========================================================================

  // ==========================================================================
  // Actions
  // ==========================================================================

  actions: {

    // TODO: add jsdoc
    onBlur (event) {
      this.set('focus', false)
    },

    // TODO: add jsdoc
    onChange (event) {
      const target = event.currentTarget || event.target
      const onInput = this.get('on-input')
      if (_.isFunction(onInput)) {
        onInput(target.value)
      } else {
        this.search(target.value)
      }
    },

    // TODO: add jsdoc
    onClickArrow (event) {
      this.toggle(event)
    },

    // TODO: add jsdoc
    onFocus () {
      this.openList()
      this.set('focus', true)
      return false
    },

    // TODO: add jsdoc
    onItemOver (event) {
      event.stopImmediatePropagation()
      let target = event.target
      let index = parseInt(target.getAttribute('data-index'), 10)
      this.set('hovered', index)
      return false
    },

    // TODO: add jsdoc
    onSelect (event) {
      event.stopPropagation()
      let target = event.currentTarget || event.target
      let index = parseInt(target.getAttribute('data-index'), 10)
      this.select(index)
    }
  }
})
<commit_msg>Addressing PR issues<commit_after>import _ from 'lodash'
import Ember from 'ember'
import computed, {readOnly} from 'ember-computed-decorators'

import layout from '../templates/components/frost-select'

// TODO: Add typedefs for items

// TODO: add jsdoc
function isAttrDifferent (newAttrs, oldAttrs, attributeName) {
  let oldValue = _.get(oldAttrs, attributeName + '.value')
  let newValue = _.get(newAttrs, attributeName + '.value')

  if (newValue && !_.isEqual(oldValue, newValue)) {
    return true
  }
  return false
}

/** Hook for handling outside element click */
function handleOutsideClick (event) {
  const $target = Ember.$(event.target)
  if (!$target.closest(this.$()).length) {
    this.onOutsideClick()
  }
}

const TAB_KEY = 9
const ESC_KEY = 27
const ENTER_KEY = 13
const UP_KEY = 38
const DOWN_KEY = 40
const BACKSPACE_KEY = 8

export default Ember.Component.extend({

  // ==========================================================================
  // Dependencies
  // ==========================================================================

  // ==========================================================================
  // Properties
  // ==========================================================================

  attributeBindings: ['tabIndex'],
  classNames: ['frost-select'],
  classNameBindings: ['focus', 'shouldOpen:open', 'disabled', 'hasError:error'],
  disabled: false,
  error: false,
  hovered: -1,
  filter: undefined,
  layout,
  maxListHeight: 400,
  tabIndex: -1,
  width: 200,

  // ==========================================================================
  // Computed Properties
  // ==========================================================================

  @readOnly
  @computed('items', 'selected', 'hovered', 'filter')
  /**
   * Get the display items
   * @param {Object[]} items - the possible items
   * @param {Number[]} selected - the array of selected indices
   * @param {Number} hovered - index currently being hovered over (or -1)
   * @param {String} filter - search filter
   * @returns {Object[]} the display items
   */
  displayItems (items, selected, hovered, filter) {
    const result = []
    const valid = this.getValid(filter)

    valid.forEach((validItem, index, list) => {
      let className = ''
      if (selected.indexOf(validItem.index) !== -1) {
        className += ' selected'
        validItem.selected = true
      }

      if (index === hovered || list.length === 1) {
        className += ' hover'
      }

      validItem.className = className
      result.push(validItem)
    })

    return result
  },

  @readOnly
  @computed('error', 'invalidFilter')
  /**
   * Computed flag for if consumer flagged us as having an error, or if the user has typed
   * something bad.
   * @param {Boolean} error - true if consumer has flagged that an error exists
   * @param {Boolean} invalidFilter - true if the user has typed something that doesn't match an option
   * @returns {Boolean} true if either error condition occured
   */
  hasError (error, invalidFilter) {
    return error || invalidFilter
  },

  @readOnly
  @computed('data', 'displayItems')
  /**
   * Flag for if the user has typed something that doesn't match any options
   * @param {Object[]} data - all the possible items
   * @param {Object[]} displayItems - the current items being displayed
   * @returns {Boolean} true if in error state
   */
  invalidFilter (data, displayItems) {
    return _.isArray(data) && (data.length > 0) && _.isArray(displayItems) && (displayItems.length === 0)
  },

  @readOnly
  @computed('data')
  /**
   * Create items from passed-in data
   * @param {Object[]} data - the possible value data passed in
   * @returns {Object[]} the items
   */
  items (data) {
    return data.map((item, index) => {
      return {
        label: item.label,
        value: item.value,
        index,
        classNames: ''
      }
    })
  },

  @readOnly
  @computed('selected')
  /**
   * Build the prompt based on the selected item(s)
   * @param {Number[]} selected - the selected indices
   * @returns {String} the prompt
   */
  prompt (selected) {
    const data = this.get('data')
    const filter = this.get('filter')
    let prompt = ''

    if (filter !== undefined) {
      prompt = filter
    } else {
      let selectedItem = data[selected[0]]
      if (selectedItem) {
        prompt = selectedItem.label
      }
    }
    return prompt
  },

  @readOnly
  @computed('invalidFilter', 'shouldDisableDropDown', 'open')
  /**
   * Determine if drop-down should open
   * @param {Boolean} invalidFilter - did the user goof?
   * @param {Boolean} shouldDisableDropDown - computed flag for whether opening the drop-down should be disabled
   * @param {Boolean} open - TODO: what is this?
   * @returns {Boolean} true if we should open
   */
  shouldOpen (invalidFilter, shouldDisableDropDown, open) {
    return !invalidFilter && !shouldDisableDropDown && open
  },

  @readOnly
  @computed('invalidFilter', 'disabled')
  /**
   * Determine if we should disable opening the drop-down
   * @param {Boolean} invalidFilter - did the user goof?
   * @param {Boolean} disabled - are we in a disabled state?
   * @returns {Boolean} true if opening should be disabled
   */
  shouldDisableDropDown (invalidFilter, disabled) {
    return invalidFilter || disabled
  },

  @readOnly
  @computed('width')
  /**
   * Compute the style attribute based on width
   * @param {Number} width - the width property
   * @returns {String} the computed style attribute
   */
  style (width) {
    return `width: ${width}px`
  },

  // ==========================================================================
  // Functions
  // ==========================================================================

  /* Ember.Component method */
  init () {
    this._super(...arguments)

    if (this.get('selected') === undefined) {
      this.set('selected', [])
    }
  },

  /* Ember.Component method */
  didReceiveAttrs (attrs) {
    this._super(...arguments)
    if (isAttrDifferent(attrs.newAttrs, attrs.oldAttrs, 'selectedValue')) {
      this.selectOptionByValue(attrs.newAttrs.selectedValue.value)
    } else if (isAttrDifferent(attrs.newAttrs, attrs.oldAttrs, 'selected')) {
      let selected = this.get('selected')

      selected = selected && (_.isArray(selected) || _.isNumber(selected)) ? [].concat(selected) : []
      this.set('selected', selected)
    }
  },

  /* Ember.didInsertElement method */
  didInsertElement () {
    this._super(...arguments)
    Ember.$(document).on('click', handleOutsideClick.bind(this))
  },

  /* Ember.willDestroyElement method */
  willDestroyElement () {
    Ember.$(document).off('click', handleOutsideClick.bind(this))
    this._super(...arguments)
  },

  // TODO: add jsdoc
  chooseHovered () {
    let displayItem = this.get('displayItems')[this.get('hovered')]
    this.select(displayItem.index)
  },

  // TODO: add jsdoc
  click (event) {
    // event.preventDefault()
  },

  // TODO: add jsdoc
  closeList () {
    this.setProperties({open: false, filter: undefined, hovered: -1})
    this.inputElement().val(this.get('prompt'))
  },

  // TODO: add jsdoc
  getLabel (item) {
    return item.label
  },

  // TODO: add jsdoc
  getValid (filter) {
    let valid = []
    this.get('items').forEach((item, index, list) => {
      if (!filter || this.getLabel(item).toLowerCase().indexOf(filter.toLowerCase()) !== -1) {
        valid.push({
          index,
          value: item.value,
          label: this.getLabel(item)
        })
      }
    })
    return valid
  },

  // TODO: add jsdoc
  getValues (selected = this.get('selected')) {
    return selected.map((selectedIndex) => {
      return this.get('data')[selectedIndex].value
    })
  },

  // TODO: add jsdoc
  hoverNext () {
    let hovered = this.get('hovered')
    if (hovered === this.get('displayItems').length - 1) {
      hovered = 0
    } else {
      hovered += 1
    }
    this.set('hovered', hovered)
  },

  // TODO: add jsdoc
  hoverPrev () {
    let hovered = this.get('hovered')
    if (hovered === 0) {
      hovered = this.get('displayItems').length - 1
    } else {
      hovered -= 1
    }
    this.set('hovered', hovered)
  },

  // TODO: add jsdoc
  inputElement () {
    return this.$('input')
  },

  /** Key down event handler
   * @param {JQuery.Event} event - event object
   */
  keyDown (event) {
    // if tab is pushed - close list
    if (event.which === TAB_KEY) {
      if (this.get('open')) {
        this.closeList()
      }
    }
  },

  // TODO: add jsdoc
  keyUp (event) {
    switch (event.which) {

      // escape key or tab key, close the dropdown
      case ESC_KEY:
        event.stopPropagation()
        if (this.get('open')) {
          this.toggle(event)
        }
        break

      // enter + spacebar, choose selected
      case ENTER_KEY:
        this.chooseHovered()
        break

      // up arrow
      case UP_KEY:
        event.preventDefault()
        this.hoverPrev()
        break

      // down arrow, open the dropdown if necessary, select next
      case DOWN_KEY:
        event.preventDefault()
        if (!this.get('open')) {
          this.openList()
        }
        this.hoverNext()
        break

      // backspace
      case BACKSPACE_KEY:
        event.preventDefault()
        if (!this.get('open')) {
          this.openList()
        }
    }
  },

  /**
   * Notify parent of currently selected values by calling the onChange callback
   * with the values of the currently selected indices
   * @param {Number[]} selected - the selected indices
   */
  notifyOfChange (selected) {
    const onChange = this.get('on-change')
    if (onChange) {
      const values = this.getValues(selected)
      onChange(values)
    }
  },

  /* obvious */
  openList () {
    this.set('open', true)
  },

  /** Handler for click outside of an element
   */
  onOutsideClick () {
    if (this.get('open')) {
      this.closeList()
    }
  },

  // TODO: add jsdoc
  search (term) {
    let valid = this.getValid(term)
    let newProps = {filter: term, hovered: -1}
    this.setProperties(newProps)
    if (valid.length === 1) {
      this.hoverNext()
    }
  },

  // TODO: add jsdoc
  select (index) {
    let selected = [index]
    let values = this.getValues(selected)
    this.setProperties({
      selected: selected,
      open: false
    })
    this.set('filter', undefined)
    if (this.get('on-change') && _.isFunction(this.get('on-change'))) {
      this.get('on-change')(values)
    }
  },

  // TODO: add jsdoc
  selectOptionByValue (selectedValue) {
    // Find index
    let valueIndex = _.findIndex(this.get('items'), (item) => _.isEqual(item.value, selectedValue))

    if (valueIndex >= 0) { // Make sure we actually found the value
      this.select(valueIndex)
    }
  },

  // TODO: add jsdoc
  toggle (event) {
    event.preventDefault()
    if (this.get('shouldDisableDropDown')) {
      return
    }

    if (this.get('open')) {
      this.closeList()
      return
    }
    this.openList()
  },

  // ==========================================================================
  // Events
  // ==========================================================================

  // ==========================================================================
  // Actions
  // ==========================================================================

  actions: {

    // TODO: add jsdoc
    onBlur (event) {
      this.set('focus', false)
    },

    // TODO: add jsdoc
    onChange (event) {
      const target = event.currentTarget || event.target
      const onInput = this.get('on-input')
      if (_.isFunction(onInput)) {
        onInput(target.value)
      } else {
        this.search(target.value)
      }
    },

    // TODO: add jsdoc
    onClickArrow (event) {
      this.toggle(event)
    },

    // TODO: add jsdoc
    onFocus () {
      this.openList()
      this.set('focus', true)
      return false
    },

    // TODO: add jsdoc
    onItemOver (event) {
      event.stopImmediatePropagation()
      let target = event.target
      let index = parseInt(target.getAttribute('data-index'), 10)
      this.set('hovered', index)
      return false
    },

    // TODO: add jsdoc
    onSelect (event) {
      event.stopPropagation()
      let target = event.currentTarget || event.target
      let index = parseInt(target.getAttribute('data-index'), 10)
      this.select(index)
    }
  }
})
<|endoftext|>


<commit_before>import Ember from 'ember';
import hbs from 'htmlbars-inline-precompile';
import StateMachine from 'ember-steps/-private/state-machine';
import { MissingPropertyError } from 'ember-steps/-private/errors';

const { Component, get, set } = Ember;
const layout = hbs`
  {{yield (hash
    step=(component 'step-manager/step'
      register-step=(action 'register-step-component')
      currentStep=transitions.currentStep
    )
    transition-to=(action 'transition-to-step')
    transition-to-next=(action 'transition-to-next-step')
    transition-to-previous=(action 'transition-to-previous-step')
    currentStep=transitions.currentStep
    loading=loading
    steps=(if _hasRendered transitions.stepArray)
  )}}
`;

export default Component.extend({
  layout,
  tagName: '',

  init() {
    this._super(...arguments);

    // Set up the state machine
    const initialStep = get(this, 'currentStep');
    if (!initialStep) {
      throw new MissingPropertyError('currentStep');
    }

    this._lastStep = initialStep;

    const stepCount = get(this, 'stepCount');
    if (!stepCount) {
      throw new MissingPropertyError('stepCount');
    }

    set(
      this,
      'transitions',
      StateMachine.create({
        initialStep
      })
    );
  },

  /**
   * @property {Ember.Object} transitions state machine for transitions
   * @private
   */
  transitions: null,

  /**
   * Whether the initial render cycle has completed
   *
   * Used to prevent a double-render-cycle when yielding an array of steps
   *
   * @property {boolean} _hasRendered
   * @private
   */
  _hasRendered: false,

  /**
   * Used internally to track the previous step
   *
   * @property {string} _lastStep
   * @private
   */
  _lastStep: undefined,

  /**
   * The `currentStep` property can be used for providing, or binding to, the
   * name of the current step.
   *
   * If you only want to provide the initial step, but do not want the target
   * object's value to be bound to it, you can either:
   *
   * - Pass it a value directly, like `currentStep='name'`
   * - Use the Unbound helper, like `currentStep=(unbound nameOfStep)`
   *
   * If you want to bind the value in both directions, you can do so by:
   *
   * - Passing a template value directly, like `currentStep=nameOfStep
   * - Using the Mut helper, like `currentStep=(mut nameOfStep)`
   *
   * Providing a mutable value is useful for cases like binding the current
   * step name to a query param.
   *
   * @property {string} currentStep the current active step
   * @public
   */
  currentStep: null,

  /**
   * This property indicates if the step-manager is performing an asynchronous
   * operation.
   *
   * It can be used to display a loader for example.
   *
   * @property {Boolean} loading
   * @public
   */
  loading: false,

  /**
   * If provided, this action will be called with a single POJO as the
   * argument, containing:
   *
   * - `value`-> The value passed to the transition action, or `undefined`
   * - `from` -> The name of the step being transitioned from
   * - `to`   -> The name of the step being transitioned to
   *
   * The action is called before the next step is activated.
   *
   * The action can return a Promise or a boolean.
   *
   * By returning a Promise, you can wait the end of an asynchronous process
   * before the transition. The transition will be prevented if the Promise rejects.
   *
   * By returning `false` from this action, you can prevent the transition
   * from taking place.
   *
   * @property {Action} will-transition
   * @public
   */
  'will-transition': null,

  /**
   * If provided, this action will be called with a single POJO as the
   * argument, containing:
   *
   * - `value`-> The value passed to the transition action, or `undefined`
   * - `from` -> The name of the step being transitioned from
   * - `to`   -> The name of the step being transitioned to
   *
   * The action is called after the next step is activated.
   *
   * @property {Action} did-transition
   * @public
   */
  'did-transition': null,

  didUpdateAttrs() {
    this._super(...arguments);

    const oldStep = this._lastStep;
    const newStep = this.get('currentStep');

    if (typeof newStep === 'undefined') {
      const firstStep = get(this, 'transitions.firstStep');
      get(this, 'transitions').activate(firstStep);
    }

    if (newStep && oldStep !== newStep) {
      get(this, 'transitions').activate(newStep);
    }

    this._lastStep = newStep;

    this._super(...arguments);
  },

  didInsertElement() {
    this._super(...arguments);

    set(this, '_hasRendered', true);
  },

  actions: {
    /**
     * Register a step with the manager
     *
     * Adds a set to the internal registry of steps by name.  If no name is
     * provided, a name will be assigned by index.
     *
     * @method registerStep
     * @param {string} name the name of the step being registered
     * @private
     */
    'register-step-component'(stepComponent) {
      const name = get(stepComponent, 'name');
      get(this, 'transitions').addStep(name);
    },

    /**
     * Used internally to transition to a specific named step
     *
     * @method make-transition
     * @param {string} to the name of the step to transition to
     * @param {string} from the name of the step being transitioned
     * @param {*} value the value to pass to the transition actions
     * @private
     */
    'make-transition'(to, from, value) {
      // Update the `currentStep` if it's mutable
      if (this.attrs.currentStep && this.attrs.currentStep.update) {
        this.attrs.currentStep.update(to);
      }

      // Activate the next step
      get(this, 'transitions').activate(to);

      if (this['did-transition']) {
        this['did-transition']({ value, from, to });
      }
    },

    /**
     * Transition to a named step
     *
     * If you have provided a `will-transition` action, it will call the action
     * before transitioning to the step.
     *
     * If the `currentStep` property was provided as a mutable value, like:
     *
     * ```js
     * {{#step-manager currentStep=(mut step) as |w|}}
     *   ...
     * {{/step-manager}}
     * ```
     *
     * Then the external property will be updated to the new step name.
     *
     * @method transition-to-step
     * @param {string} to the name of the step to transition to
     * @param {*} value the value to pass to the transition actions
     * @public
     */
    'transition-to-step'(to, value) {
      const from = get(this, 'transitions.currentStep');
      const validator = this['will-transition'];

      // Prevent multiple transitions
      if (get(this, 'loading')) {
        return;
      }

      if (validator && typeof validator === 'function') {
        const result = validator({ value, from, to });

        if (result) {
          if (typeof result.then === 'function') {
            set(this, 'loading', true);
            result
              .then(() => {
                this.send('make-transition', to, from, value);
              }, null)
              .finally(() => {
                set(this, 'loading', false);
              });
            return;
          }
        } else if (result === false) {
          return;
        }
      }

      this.send('make-transition', to, from, value);
    },

    /**
     * Transition to the "next" step
     *
     * When called, this action will advance from the current step to the next
     * one, as defined by the order of their insertion into the DOM (AKA, the
     * order in the template).
     *
     * The last step will transition back to the first one.
     *
     * @method transition-to-next-step
     * @param {*} value the value to pass to the transition actions
     * @public
     */
    'transition-to-next-step'(value) {
      const to = get(this, 'transitions').peek();

      this.send('transition-to-step', to, value);
    },

    /**
     * Transition to the "previous" step
     *
     * When called, this action will go back to the previous step according to
     * the step which was visited before entering the currentStep
     *
     * The first step will not transition to anything.
     *
     * @method transition-to-previous-step
     * @param {*} value the value to pass to the transition actions
     * @public
     */
    'transition-to-previous-step'(value) {
      const to = get(this, '_lastStep');

      if (to) {
        this.send('transition-to-step', to, value);
      }
    }
  }
});
<commit_msg>FIX: unnecessary nested if<commit_after>import Ember from 'ember';
import hbs from 'htmlbars-inline-precompile';
import StateMachine from 'ember-steps/-private/state-machine';
import { MissingPropertyError } from 'ember-steps/-private/errors';

const { Component, get, set } = Ember;
const layout = hbs`
  {{yield (hash
    step=(component 'step-manager/step'
      register-step=(action 'register-step-component')
      currentStep=transitions.currentStep
    )
    transition-to=(action 'transition-to-step')
    transition-to-next=(action 'transition-to-next-step')
    transition-to-previous=(action 'transition-to-previous-step')
    currentStep=transitions.currentStep
    loading=loading
    steps=(if _hasRendered transitions.stepArray)
  )}}
`;

export default Component.extend({
  layout,
  tagName: '',

  init() {
    this._super(...arguments);

    // Set up the state machine
    const initialStep = get(this, 'currentStep');
    if (!initialStep) {
      throw new MissingPropertyError('currentStep');
    }

    this._lastStep = initialStep;

    const stepCount = get(this, 'stepCount');
    if (!stepCount) {
      throw new MissingPropertyError('stepCount');
    }

    set(
      this,
      'transitions',
      StateMachine.create({
        initialStep
      })
    );
  },

  /**
   * @property {Ember.Object} transitions state machine for transitions
   * @private
   */
  transitions: null,

  /**
   * Whether the initial render cycle has completed
   *
   * Used to prevent a double-render-cycle when yielding an array of steps
   *
   * @property {boolean} _hasRendered
   * @private
   */
  _hasRendered: false,

  /**
   * Used internally to track the previous step
   *
   * @property {string} _lastStep
   * @private
   */
  _lastStep: undefined,

  /**
   * The `currentStep` property can be used for providing, or binding to, the
   * name of the current step.
   *
   * If you only want to provide the initial step, but do not want the target
   * object's value to be bound to it, you can either:
   *
   * - Pass it a value directly, like `currentStep='name'`
   * - Use the Unbound helper, like `currentStep=(unbound nameOfStep)`
   *
   * If you want to bind the value in both directions, you can do so by:
   *
   * - Passing a template value directly, like `currentStep=nameOfStep
   * - Using the Mut helper, like `currentStep=(mut nameOfStep)`
   *
   * Providing a mutable value is useful for cases like binding the current
   * step name to a query param.
   *
   * @property {string} currentStep the current active step
   * @public
   */
  currentStep: null,

  /**
   * This property indicates if the step-manager is performing an asynchronous
   * operation.
   *
   * It can be used to display a loader for example.
   *
   * @property {Boolean} loading
   * @public
   */
  loading: false,

  /**
   * If provided, this action will be called with a single POJO as the
   * argument, containing:
   *
   * - `value`-> The value passed to the transition action, or `undefined`
   * - `from` -> The name of the step being transitioned from
   * - `to`   -> The name of the step being transitioned to
   *
   * The action is called before the next step is activated.
   *
   * The action can return a Promise or a boolean.
   *
   * By returning a Promise, you can wait the end of an asynchronous process
   * before the transition. The transition will be prevented if the Promise rejects.
   *
   * By returning `false` from this action, you can prevent the transition
   * from taking place.
   *
   * @property {Action} will-transition
   * @public
   */
  'will-transition': null,

  /**
   * If provided, this action will be called with a single POJO as the
   * argument, containing:
   *
   * - `value`-> The value passed to the transition action, or `undefined`
   * - `from` -> The name of the step being transitioned from
   * - `to`   -> The name of the step being transitioned to
   *
   * The action is called after the next step is activated.
   *
   * @property {Action} did-transition
   * @public
   */
  'did-transition': null,

  didUpdateAttrs() {
    this._super(...arguments);

    const oldStep = this._lastStep;
    const newStep = this.get('currentStep');

    if (typeof newStep === 'undefined') {
      const firstStep = get(this, 'transitions.firstStep');
      get(this, 'transitions').activate(firstStep);
    }

    if (newStep && oldStep !== newStep) {
      get(this, 'transitions').activate(newStep);
    }

    this._lastStep = newStep;

    this._super(...arguments);
  },

  didInsertElement() {
    this._super(...arguments);

    set(this, '_hasRendered', true);
  },

  actions: {
    /**
     * Register a step with the manager
     *
     * Adds a set to the internal registry of steps by name.  If no name is
     * provided, a name will be assigned by index.
     *
     * @method registerStep
     * @param {string} name the name of the step being registered
     * @private
     */
    'register-step-component'(stepComponent) {
      const name = get(stepComponent, 'name');
      get(this, 'transitions').addStep(name);
    },

    /**
     * Used internally to transition to a specific named step
     *
     * @method make-transition
     * @param {string} to the name of the step to transition to
     * @param {string} from the name of the step being transitioned
     * @param {*} value the value to pass to the transition actions
     * @private
     */
    'make-transition'(to, from, value) {
      // Update the `currentStep` if it's mutable
      if (this.attrs.currentStep && this.attrs.currentStep.update) {
        this.attrs.currentStep.update(to);
      }

      // Activate the next step
      get(this, 'transitions').activate(to);

      if (this['did-transition']) {
        this['did-transition']({ value, from, to });
      }
    },

    /**
     * Transition to a named step
     *
     * If you have provided a `will-transition` action, it will call the action
     * before transitioning to the step.
     *
     * If the `currentStep` property was provided as a mutable value, like:
     *
     * ```js
     * {{#step-manager currentStep=(mut step) as |w|}}
     *   ...
     * {{/step-manager}}
     * ```
     *
     * Then the external property will be updated to the new step name.
     *
     * @method transition-to-step
     * @param {string} to the name of the step to transition to
     * @param {*} value the value to pass to the transition actions
     * @public
     */
    'transition-to-step'(to, value) {
      const from = get(this, 'transitions.currentStep');
      const validator = this['will-transition'];

      // Prevent multiple transitions
      if (get(this, 'loading')) {
        return;
      }

      if (validator && typeof validator === 'function') {
        const result = validator({ value, from, to });

        if (result && typeof result.then === 'function') {
          set(this, 'loading', true);
          result
            .then(() => {
              this.send('make-transition', to, from, value);
            }, null)
            .finally(() => {
              set(this, 'loading', false);
            });
          return;
        } else if (result === false) {
          return;
        }
      }

      this.send('make-transition', to, from, value);
    },

    /**
     * Transition to the "next" step
     *
     * When called, this action will advance from the current step to the next
     * one, as defined by the order of their insertion into the DOM (AKA, the
     * order in the template).
     *
     * The last step will transition back to the first one.
     *
     * @method transition-to-next-step
     * @param {*} value the value to pass to the transition actions
     * @public
     */
    'transition-to-next-step'(value) {
      const to = get(this, 'transitions').peek();

      this.send('transition-to-step', to, value);
    },

    /**
     * Transition to the "previous" step
     *
     * When called, this action will go back to the previous step according to
     * the step which was visited before entering the currentStep
     *
     * The first step will not transition to anything.
     *
     * @method transition-to-previous-step
     * @param {*} value the value to pass to the transition actions
     * @public
     */
    'transition-to-previous-step'(value) {
      const to = get(this, '_lastStep');

      if (to) {
        this.send('transition-to-step', to, value);
      }
    }
  }
});
<|endoftext|>