<commit_before>var path = require('path'),
  fs = require('fs'),
  os = require('os'),
  stream = require('stream'),
  AWS = require('aws-sdk'),
  Promise = require('bluebird'),
  _ = require('lodash');

/**
 * Constructs and returns a new bloody simple S3 client.
 * @param {object} options S3 client options.
 * @param {string} options.bucket name of the S3 bucket to connect to.
 * @param {string} options.accessKeyId your AWS access key ID.
 * @param {string} options.secretAccessKey your AWS secret access key.
 * @param {string} [options.region=us-east-1] AWS region, defaults to us-east-1.
 * @param {boolean} [options.sslEnabled=true] whether to enable SSL for requests.
 * @throws {Error} if options are invalid.
 * @constructor
 */
function BloodySimpleS3(options) {
  // make sure options is valid
  if (!_.isPlainObject(options)) {
    throw new Error('Invalid options param; expected object, received ' + typeof(options));
  }

  // set default options
  options = _.defaults(options, {
    region: 'us-east-1',
    sslEnabled: true
  });

  // make sure individual options are valid
  if (!_.isString(options.bucket)) {
    throw new Error(
      'Invalid S3 bucket option; ' +
      'expected string, received ' + typeof(options.bucket)
    );
  }

  if (!_.isString(options.accessKeyId)) {
    throw new Error(
      'Invalid accessKeyId option; ' +
      'expected string, received ' + typeof(options.accessKeyId)
    );
  }

  if (!_.isString(options.secretAccessKey)) {
    throw new Error(
      'Invalid secretAccessKey option; ' +
      'expected string, received ' + typeof(options.secretAccessKey)
    );
  }

  if (!_.isString(options.region)) {
    throw new Error(
      'Invalid region option; ' +
      'expected string, received ' + typeof(options.region)
    );
  }

  if (!_.isBoolean(options.sslEnabled)) {
    throw new Error(
      'Invalid sslEnabled option; ' +
      'expected boolean, received ' + typeof(options.sslEnabled)
    );
  }

  this.bucket = options.bucket;
  this.s3 = new AWS.S3({
    accessKeyId: options.accessKeyId,
    secretAccessKey: options.secretAccessKey,
    region: options.region,
    sslEnabled: options.sslEnabled,
    apiVersion: '2006-03-01'
  });
}

/**
 * Creates and returns a readable stream to the designated file on S3.
 * @param {string} key relative path within the S3 bucket.
 * @return {ReadableStream}
 * @throws {Error} if key is invalid.
 */
BloodySimpleS3.prototype.createReadStream = function (key) {
  var params;

  // make sure key is valid
  if (!_.isString(key)) {
    throw new Error(
      'Invalid key param; ' +
      'expected string, received ' + typeof(key)
    );
  }

  params = {
    Key: key,
    Bucket: this.bucket
  };

  return this.s3.getObject(params).createReadStream();
};

/**
 * Downloads the designated file from S3 to local filesystem.
 * @param {string} key relative path within the S3 bucket.
 * @param {object} [options] request options.
 * @param {string} [options.destination=os.tmpdir()] destination path, i.e. a folder or file.
 * @param {function} [callback] optional callback function, i.e. function(err, path).
 * @return {Promise}
 */
BloodySimpleS3.prototype.download = function (key, options, callback) {
  var self = this, resolver;

  // make sure key param is valid
  if (!_.isString(key)) {
    return Promise.reject(new Error(
      'Invalid key param; ' +
      'expected string, received ' + typeof(key)
    )).nodeify(callback);
  }

  // handle optional params
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  // set options defaults
  options = _.defaults(options, {
    destination: os.tmpdir()
  });

  resolver = function(resolve, reject) {
    fs.stat(options.destination, function (err, stats) {
      var file, writable, readable;

      if (err) return reject(err);

      // make sure destination is either file or folder
      if (stats.isDirectory()) {
        file = path.join(options.destination, path.basename(key));
      } else if (stats.isFile()) {
        file = options.destination;
      } else {
        return reject(new Error(
          'Invalid destination path; ' +
          'expected folder or file'
        ));
      }

      readable = self.createReadStream(key);
      writable = fs.createWriteStream(file);
      readable.pipe(writable);

      readable.on('error', reject);
      writable.on('finish', function () {
        resolve(file);
      });
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Creates of updates the designated file on S3, consuming a readable stream.
 * @param {string} key relative path within the S3 bucket.
 * @param {ReadableStream} readable the readable stream to pull the file data.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.writeFileStream = function (key, readable, callback) {
  var self = this, params, resolver;

  // make sure key param is valid
  if (!_.isString(key)) {
    return Promise.reject(new Error(
      'Invalid key param; ' +
      'expected string, received ' + typeof(key)
    )).nodeify(callback);
  }

  // make sure readable param is valid
  if (!(readable instanceof stream.Readable)) {
    return Promise.reject(new Error(
      'Invalid readable param; ' +
      'expected a ReadableStream instance, received ' + typeof(readable)
    )).nodeify(callback);
  }

  params = {
    Key: key,
    Body: readable,
    Bucket: self.bucket
  };

  resolver = function(resolve, reject) {
    self.s3.putObject(params, function (err) {
      if (err) return reject(err);
      resolve({
        key: key
      });
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Uploads the designated file to S3.
 * @param {string} file absolute/relative path to file in local disk.
 * @param {object} [options] upload options.
 * @param {string} [options.key] the file name to store in S3.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.upload = function (file, options, callback) {
  var self = this, resolver;

  // make sure file param is valid
  if (!_.isString(file)) {
    return Promise.reject(new Error(
      'Invalid file param; ' +
      'expected string, received ' + typeof(file)
    )).nodeify(callback);
  }

  // handle optional params
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  // resolve relative file
  file = path.resolve(__dirname, file);

  // set default values of options
  options = _.defaults(options, {
    key: path.basename(file)
  });

  resolver = function(resolve, reject) {
    fs.stat(file, function (err, stats) {
      var readable;

      if (err) return reject(err);

      // make sure file is referencing a file
      if (!stats.isFile()) {
        return reject(new Error(
          'File path is invalid; ' +
          'you need to reference an actual file'
        ));
      }

      readable = fs.createReadStream(file);

      resolve(self.writeFileStream(options.key, readable));
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Returns an array of (up to 1000) files in the designated directory.
 * @param {string} dir relative directory path within the S3 bucket.
 * @param {object} [options] list options.
 * @param {string} [options.cursor] the key to start with listing files.
 * @param {number} [options.limit] maximum number of files to list, must not exceed 1000.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.list = function (dir, options, callback) {
  var self = this, params, resolver;

  // make sure dir param is valid
  if (!_.isString(dir)) {
    return Promise.reject(new Error(
      'Invalid dir param; ' +
      'expected string, received ' + typeof(dir)
    )).nodeify(callback);
  }

  // handle options param
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  params = {
    Bucket: this.bucket,
    Prefix: path.normalize(dir),
    Marker: options.cursor,
    MaxKeys: options.limit
  };

  resolver = function(resolve, reject) {
    self.s3.listObjects(params, function(err, data) {
      var arr;

      if (err) return reject(err);

      arr = data.Contents.map(function (obj) {
        return {
          key: obj.Key,
          size: obj.Size,
          lastModified: obj.LastModified
        };
      });

      resolve(arr);
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Creates a copy of a file that is already stored in Amazon S3.
 * @param {string} source relative path of the source file within the S3 bucket.
 * @param {string} destination relative path of the copy within the S3 bucket.
 * @param {object} [options] copy options.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.copy = function (source, destination, options, callback) {
  var self = this, params, resolver;

  // make sure source param is valid
  if (!_.isString(source)) return Promise.reject(new Error(
    'Invalid source param; ' +
    'expected string, received ' + typeof(source)
  )).nodeify(callback);

  // make sure destination param is valid
  if (!_.isString(destination)) return Promise.reject(new Error(
    'Invalid destination param; ' +
    'expected string, received ' + typeof(destination)
  )).nodeify(callback);

  // handle options param
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  params = _.assign(options, {
    Bucket: this.bucket,
    CopySource: encodeURI(path.join(this.bucket, source)),
    Key: destination,
    MetadataDirective: 'COPY'
  });

  resolver = function(resolve, reject) {
    self.s3.copyObject(params, function(err, data) {
      if (err) return reject(err);
      resolve({
        key: destination,
        lastModified: data.LastModified
      });
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Removes the designated file from Amazon S3.
 * @param {string} key relative path within the S3 bucket.
 * @param {object} [options] remove options.
 * @param {function} [callback] optional callback function, i.e. function(err).
 * @return {Promise}
 */
BloodySimpleS3.prototype.remove = function (key, callback) {
  var self = this, params, resolver;

  // make sure key param is valid
  if (!_.isString(key)) return Promise.reject(new Error(
    'Invalid key param; ' +
    'expected string, received ' + typeof(key)
  )).nodeify(callback);

  params = {
    Bucket: this.bucket,
    Key: key
  };

  resolver = function(resolve, reject) {
    self.s3.deleteObject(params, function(err) {
      if (err) return reject(err);
      resolve();
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Moves/renames a file in Amazon S3.
 * @param {string} source relative path of the source file within the S3 bucket.
 * @param {string} destination relative path of the copy within the S3 bucket.
 * @param {object} [options] move options (similar to copy options).
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.move = function (source, destination, options, callback) {
  return this.copy(source, destination)
    .bind(this)
    .then(function (data) {
      return this.remove(source).return(data);
    })
    .nodeify(callback);
};

module.exports = BloodySimpleS3;

require('dotenv').load();

var s3 = new BloodySimpleS3({
  bucket: 'sdk-analytics',
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  sslEnabled: true
});

s3.list('./temp')
  .then(function (arr) {
    console.log(arr);
    return s3.copy(arr[1].key, 'temp/0a')
      .then(function (data) {
        console.log(data);
        return s3.remove('temp/0a');
      })
      .then(function (data) {
        console.log(data);
      });
  })
  .catch(function (err) {
    console.error(err);
  });

// s3.upload({
//   source: path.resolve(__dirname, '../LICENSE'),
//   key: 'apk/test'
// }).then(function (data) {
//   console.log(data);
// }).catch(function (err) {
//   console.error(err);
// });
<commit_msg>Comment test code.<commit_after>var path = require('path'),
  fs = require('fs'),
  os = require('os'),
  stream = require('stream'),
  AWS = require('aws-sdk'),
  Promise = require('bluebird'),
  _ = require('lodash');

/**
 * Constructs and returns a new bloody simple S3 client.
 * @param {object} options S3 client options.
 * @param {string} options.bucket name of the S3 bucket to connect to.
 * @param {string} options.accessKeyId your AWS access key ID.
 * @param {string} options.secretAccessKey your AWS secret access key.
 * @param {string} [options.region=us-east-1] AWS region, defaults to us-east-1.
 * @param {boolean} [options.sslEnabled=true] whether to enable SSL for requests.
 * @throws {Error} if options are invalid.
 * @constructor
 */
function BloodySimpleS3(options) {
  // make sure options is valid
  if (!_.isPlainObject(options)) {
    throw new Error('Invalid options param; expected object, received ' + typeof(options));
  }

  // set default options
  options = _.defaults(options, {
    region: 'us-east-1',
    sslEnabled: true
  });

  // make sure individual options are valid
  if (!_.isString(options.bucket)) {
    throw new Error(
      'Invalid S3 bucket option; ' +
      'expected string, received ' + typeof(options.bucket)
    );
  }

  if (!_.isString(options.accessKeyId)) {
    throw new Error(
      'Invalid accessKeyId option; ' +
      'expected string, received ' + typeof(options.accessKeyId)
    );
  }

  if (!_.isString(options.secretAccessKey)) {
    throw new Error(
      'Invalid secretAccessKey option; ' +
      'expected string, received ' + typeof(options.secretAccessKey)
    );
  }

  if (!_.isString(options.region)) {
    throw new Error(
      'Invalid region option; ' +
      'expected string, received ' + typeof(options.region)
    );
  }

  if (!_.isBoolean(options.sslEnabled)) {
    throw new Error(
      'Invalid sslEnabled option; ' +
      'expected boolean, received ' + typeof(options.sslEnabled)
    );
  }

  this.bucket = options.bucket;
  this.s3 = new AWS.S3({
    accessKeyId: options.accessKeyId,
    secretAccessKey: options.secretAccessKey,
    region: options.region,
    sslEnabled: options.sslEnabled,
    apiVersion: '2006-03-01'
  });
}

/**
 * Creates and returns a readable stream to the designated file on S3.
 * @param {string} key relative path within the S3 bucket.
 * @return {ReadableStream}
 * @throws {Error} if key is invalid.
 */
BloodySimpleS3.prototype.createReadStream = function (key) {
  var params;

  // make sure key is valid
  if (!_.isString(key)) {
    throw new Error(
      'Invalid key param; ' +
      'expected string, received ' + typeof(key)
    );
  }

  params = {
    Key: key,
    Bucket: this.bucket
  };

  return this.s3.getObject(params).createReadStream();
};

/**
 * Downloads the designated file from S3 to local filesystem.
 * @param {string} key relative path within the S3 bucket.
 * @param {object} [options] request options.
 * @param {string} [options.destination=os.tmpdir()] destination path, i.e. a folder or file.
 * @param {function} [callback] optional callback function, i.e. function(err, path).
 * @return {Promise}
 */
BloodySimpleS3.prototype.download = function (key, options, callback) {
  var self = this, resolver;

  // make sure key param is valid
  if (!_.isString(key)) {
    return Promise.reject(new Error(
      'Invalid key param; ' +
      'expected string, received ' + typeof(key)
    )).nodeify(callback);
  }

  // handle optional params
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  // set options defaults
  options = _.defaults(options, {
    destination: os.tmpdir()
  });

  resolver = function(resolve, reject) {
    fs.stat(options.destination, function (err, stats) {
      var file, writable, readable;

      if (err) return reject(err);

      // make sure destination is either file or folder
      if (stats.isDirectory()) {
        file = path.join(options.destination, path.basename(key));
      } else if (stats.isFile()) {
        file = options.destination;
      } else {
        return reject(new Error(
          'Invalid destination path; ' +
          'expected folder or file'
        ));
      }

      readable = self.createReadStream(key);
      writable = fs.createWriteStream(file);
      readable.pipe(writable);

      readable.on('error', reject);
      writable.on('finish', function () {
        resolve(file);
      });
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Creates of updates the designated file on S3, consuming a readable stream.
 * @param {string} key relative path within the S3 bucket.
 * @param {ReadableStream} readable the readable stream to pull the file data.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.writeFileStream = function (key, readable, callback) {
  var self = this, params, resolver;

  // make sure key param is valid
  if (!_.isString(key)) {
    return Promise.reject(new Error(
      'Invalid key param; ' +
      'expected string, received ' + typeof(key)
    )).nodeify(callback);
  }

  // make sure readable param is valid
  if (!(readable instanceof stream.Readable)) {
    return Promise.reject(new Error(
      'Invalid readable param; ' +
      'expected a ReadableStream instance, received ' + typeof(readable)
    )).nodeify(callback);
  }

  params = {
    Key: key,
    Body: readable,
    Bucket: self.bucket
  };

  resolver = function(resolve, reject) {
    self.s3.putObject(params, function (err) {
      if (err) return reject(err);
      resolve({key: key});
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Uploads the designated file to S3.
 * @param {string} file absolute/relative path to file in local disk.
 * @param {object} [options] upload options.
 * @param {string} [options.key] the file name to store in S3.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.upload = function (file, options, callback) {
  var self = this, resolver;

  // make sure file param is valid
  if (!_.isString(file)) {
    return Promise.reject(new Error(
      'Invalid file param; ' +
      'expected string, received ' + typeof(file)
    )).nodeify(callback);
  }

  // handle optional params
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  // resolve relative file
  file = path.resolve(__dirname, file);

  // set default values of options
  options = _.defaults(options, {
    key: path.basename(file)
  });

  resolver = function(resolve, reject) {
    fs.stat(file, function (err, stats) {
      var readable;

      if (err) return reject(err);

      // make sure file is referencing a file
      if (!stats.isFile()) {
        return reject(new Error(
          'File path is invalid; ' +
          'you need to reference an actual file'
        ));
      }

      readable = fs.createReadStream(file);

      resolve(self.writeFileStream(options.key, readable));
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Returns an array of (up to 1000) files in the designated directory.
 * @param {string} dir relative directory path within the S3 bucket.
 * @param {object} [options] list options.
 * @param {string} [options.cursor] the key to start with listing files.
 * @param {number} [options.limit] maximum number of files to list, must not exceed 1000.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.list = function (dir, options, callback) {
  var self = this, params, resolver;

  // make sure dir param is valid
  if (!_.isString(dir)) {
    return Promise.reject(new Error(
      'Invalid dir param; ' +
      'expected string, received ' + typeof(dir)
    )).nodeify(callback);
  }

  // handle options param
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  params = {
    Bucket: this.bucket,
    Prefix: path.normalize(dir),
    Marker: options.cursor,
    MaxKeys: options.limit
  };

  resolver = function(resolve, reject) {
    self.s3.listObjects(params, function(err, data) {
      var arr;

      if (err) return reject(err);

      arr = data.Contents.map(function (obj) {
        return {
          key: obj.Key,
          size: obj.Size,
          lastModified: obj.LastModified
        };
      });

      resolve(arr);
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Creates a copy of a file that is already stored in Amazon S3.
 * @param {string} source relative path of the source file within the S3 bucket.
 * @param {string} destination relative path of the copy within the S3 bucket.
 * @param {object} [options] copy options.
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.copy = function (source, destination, options, callback) {
  var self = this, params, resolver;

  // make sure source param is valid
  if (!_.isString(source)) return Promise.reject(new Error(
    'Invalid source param; ' +
    'expected string, received ' + typeof(source)
  )).nodeify(callback);

  // make sure destination param is valid
  if (!_.isString(destination)) return Promise.reject(new Error(
    'Invalid destination param; ' +
    'expected string, received ' + typeof(destination)
  )).nodeify(callback);

  // handle options param
  if (!_.isPlainObject(options)) {
    if (_.isFunction(options)) {
      callback = options;
    } else if (!_.isUndefined(options)) {
      return Promise.reject(new Error(
        'Invalid options param; ' +
        'expected object, received ' + typeof(options)
      )).nodeify(callback);
    }

    options = {};
  }

  params = _.assign(options, {
    Bucket: this.bucket,
    CopySource: encodeURI(path.join(this.bucket, source)),
    Key: destination,
    MetadataDirective: 'COPY'
  });

  resolver = function(resolve, reject) {
    self.s3.copyObject(params, function(err, data) {
      if (err) return reject(err);
      resolve({
        key: destination,
        lastModified: data.LastModified
      });
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Removes the designated file from Amazon S3.
 * @param {string} key relative path within the S3 bucket.
 * @param {object} [options] remove options.
 * @param {function} [callback] optional callback function, i.e. function(err).
 * @return {Promise}
 */
BloodySimpleS3.prototype.remove = function (key, callback) {
  var self = this, params, resolver;

  // make sure key param is valid
  if (!_.isString(key)) return Promise.reject(new Error(
    'Invalid key param; ' +
    'expected string, received ' + typeof(key)
  )).nodeify(callback);

  params = {
    Bucket: this.bucket,
    Key: key
  };

  resolver = function(resolve, reject) {
    self.s3.deleteObject(params, function(err) {
      if (err) return reject(err);
      resolve();
    });
  };

  return new Promise(resolver).nodeify(callback);
};

/**
 * Moves/renames a file in Amazon S3.
 * @param {string} source relative path of the source file within the S3 bucket.
 * @param {string} destination relative path of the copy within the S3 bucket.
 * @param {object} [options] move options (similar to copy options).
 * @param {function} [callback] optional callback function, i.e. function(err, data).
 * @return {Promise}
 */
BloodySimpleS3.prototype.move = function (source, destination, options, callback) {
  return this.copy(source, destination)
    .bind(this)
    .then(function (data) {
      return this.remove(source).return(data);
    })
    .nodeify(callback);
};

module.exports = BloodySimpleS3;

// require('dotenv').load();

// var s3 = new BloodySimpleS3({
//   bucket: 'sdk-analytics',
//   accessKeyId: process.env.AWS_ACCESS_KEY_ID,
//   secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
//   sslEnabled: true
// });

// s3.list('./temp')
//   .then(function (arr) {
//     console.log(arr);
//     return s3.copy(arr[1].key, 'temp/0a')
//       .then(function (data) {
//         console.log(data);
//         return s3.remove('temp/0a');
//       })
//       .then(function (data) {
//         console.log(data);
//       });
//   })
//   .catch(function (err) {
//     console.error(err);
//   });

// s3.upload({
//   source: path.resolve(__dirname, '../LICENSE'),
//   key: 'apk/test'
// }).then(function (data) {
//   console.log(data);
// }).catch(function (err) {
//   console.error(err);
// });
<|endoftext|>


<commit_before>'use strict';

function $IntervalProvider() {
  this.$get = ['$rootScope', '$window', '$q', '$$q',
       function($rootScope,   $window,   $q,   $$q) {
    var intervals = {};

     /**
      * @ngdoc service
      * @name $interval
      *
      * @description
      * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
      * milliseconds.
      *
      * The return value of registering an interval function is a promise. This promise will be
      * notified upon each tick of the interval, and will be resolved after `count` iterations, or
      * run indefinitely if `count` is not defined. The value of the notification will be the
      * number of iterations that have run.
      * To cancel an interval, call `$interval.cancel(promise)`.
      *
      * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
      * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
      * time.
      *
      *

      * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
      * with them.  In particular they are not automatically destroyed when a controller's scope or a
      * directive's element are destroyed.
      * You should take this into consideration and make sure to always cancel the interval at the
      * appropriate moment.  See the example below for more details on how and when to do this.
      *

      *
      * @param {function()} fn A function that should be called repeatedly.
      * @param {number} delay Number of milliseconds between each function call.
      * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
      *   indefinitely.
      * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
      *   will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
      * @returns {promise} A promise which will be notified on each iteration.
      *
      * @example
      * <example module="intervalExample">
      * <file name="index.html">
      *
      *
      *

      *

      *       Date format: <input ng-model="format">

      *       Current time is:
      *

      *       Blood 1 : {{blood_1}}
      *       Blood 2 : {{blood_2}}
      *       <button type="button" data-ng-click="fight()">Fight</button>
      *       <button type="button" data-ng-click="stopFight()">StopFight</button>
      *       <button type="button" data-ng-click="resetFight()">resetFight</button>
      *

      *

      *
      * </file>
      * </example>
      */
    function interval(fn, delay, count, invokeApply) {
      var setInterval = $window.setInterval,
          clearInterval = $window.clearInterval,
          iteration = 0,
          skipApply = (isDefined(invokeApply) && !invokeApply),
          deferred = (skipApply ? $$q : $q).defer(),
          promise = deferred.promise;

      count = isDefined(count) ? count : 0;

      promise.then(null, null, fn);

      promise.$$intervalId = setInterval(function tick() {
        deferred.notify(iteration++);

        if (count > 0 && iteration >= count) {
          deferred.resolve(iteration);
          clearInterval(promise.$$intervalId);
          delete intervals[promise.$$intervalId];
        }

        if (!skipApply) $rootScope.$apply();

      }, delay);

      intervals[promise.$$intervalId] = deferred;

      return promise;
    }

     /**
      * @ngdoc method
      * @name $interval#cancel
      *
      * @description
      * Cancels a task associated with the `promise`.
      *
      * @param {promise} promise returned by the `$interval` function.
      * @returns {boolean} Returns `true` if the task was successfully canceled.
      */
    interval.cancel = function(promise) {
      if (promise && promise.$$intervalId in intervals) {
        intervals[promise.$$intervalId].reject('canceled');
        $window.clearInterval(promise.$$intervalId);
        delete intervals[promise.$$intervalId];
        return true;
      }
      return false;
    };

    return interval;
  }];
}
<commit_msg>docs($interval): fix typo in example<commit_after>'use strict';

function $IntervalProvider() {
  this.$get = ['$rootScope', '$window', '$q', '$$q',
       function($rootScope,   $window,   $q,   $$q) {
    var intervals = {};

     /**
      * @ngdoc service
      * @name $interval
      *
      * @description
      * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
      * milliseconds.
      *
      * The return value of registering an interval function is a promise. This promise will be
      * notified upon each tick of the interval, and will be resolved after `count` iterations, or
      * run indefinitely if `count` is not defined. The value of the notification will be the
      * number of iterations that have run.
      * To cancel an interval, call `$interval.cancel(promise)`.
      *
      * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
      * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
      * time.
      *
      *

      * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
      * with them.  In particular they are not automatically destroyed when a controller's scope or a
      * directive's element are destroyed.
      * You should take this into consideration and make sure to always cancel the interval at the
      * appropriate moment.  See the example below for more details on how and when to do this.
      *

      *
      * @param {function()} fn A function that should be called repeatedly.
      * @param {number} delay Number of milliseconds between each function call.
      * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
      *   indefinitely.
      * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
      *   will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
      * @returns {promise} A promise which will be notified on each iteration.
      *
      * @example
      * <example module="intervalExample">
      * <file name="index.html">
      *
      *
      *

      *

      *       Date format: <input ng-model="format">

      *       Current time is:
      *

      *       Blood 1 : {{blood_1}}
      *       Blood 2 : {{blood_2}}
      *       <button type="button" data-ng-click="fight()">Fight</button>
      *       <button type="button" data-ng-click="stopFight()">StopFight</button>
      *       <button type="button" data-ng-click="resetFight()">resetFight</button>
      *

      *

      *
      * </file>
      * </example>
      */
    function interval(fn, delay, count, invokeApply) {
      var setInterval = $window.setInterval,
          clearInterval = $window.clearInterval,
          iteration = 0,
          skipApply = (isDefined(invokeApply) && !invokeApply),
          deferred = (skipApply ? $$q : $q).defer(),
          promise = deferred.promise;

      count = isDefined(count) ? count : 0;

      promise.then(null, null, fn);

      promise.$$intervalId = setInterval(function tick() {
        deferred.notify(iteration++);

        if (count > 0 && iteration >= count) {
          deferred.resolve(iteration);
          clearInterval(promise.$$intervalId);
          delete intervals[promise.$$intervalId];
        }

        if (!skipApply) $rootScope.$apply();

      }, delay);

      intervals[promise.$$intervalId] = deferred;

      return promise;
    }

     /**
      * @ngdoc method
      * @name $interval#cancel
      *
      * @description
      * Cancels a task associated with the `promise`.
      *
      * @param {promise} promise returned by the `$interval` function.
      * @returns {boolean} Returns `true` if the task was successfully canceled.
      */
    interval.cancel = function(promise) {
      if (promise && promise.$$intervalId in intervals) {
        intervals[promise.$$intervalId].reject('canceled');
        $window.clearInterval(promise.$$intervalId);
        delete intervals[promise.$$intervalId];
        return true;
      }
      return false;
    };

    return interval;
  }];
}
<|endoftext|>


<commit_before>/**
 * ShopReactNative
 *
 * @author Tony Wong
 * @date 2016-08-13
 * @email 908601756@qq.com
 * @copyright Copyright © 2016 EleTeam
 * @license The MIT License (MIT)
 */

'use strict';

import React from 'react';
import {
    StyleSheet,
    View,
    ListView,
    Text,
    InteractionManager,
    TouchableOpacity,
    Alert
} from 'react-native';
import {categoryListWithProduct} from '../actions/productActions';
import Loading from '../components/Loading';

let isLoading = true;

export default class CategoryPage extends React.Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        //交互管理器在任意交互/动画完成之后，允许安排长期的运行工作. 在所有交互都完成之后安排一个函数来运行。
        InteractionManager.runAfterInteractions(() => {
            const {dispatch} = this.props;
            dispatch(categoryListWithProduct(isLoading));
        });
    }

    render() {
        const {productReducer} = this.props;
        let categories = productReducer.categories;
        // alert(productReducer.isLoading);
        return (
            <View style={styles.container}>
                {productReducer.isLoading ?
                    <Loading /> :
                    <View style={styles.container}>
                        <CategoryList categories={categories}/>
                        <ProductList categories={categories}/>
                    </View>
                }
            </View>
        )
    }
}

class CategoryList extends React.Component {
    constructor(props) {
        super(props);
        // Alert.alert(this.props.categories);

        this._renderRow = this._renderRow.bind(this);

        let dataSource = new ListView.DataSource({
            getRowData: (data, sectionId, rowId) => {
                return data[sectionId][rowId];
            },
            getSectionHeaderData: (data, sectionId) => {
                return data[sectionId];
            },
            rowHasChanged: (row1, row2) => row1 !== row2,
            sectionHeaderHasChanged: (section1, section2) => section1 !== section2,
        });

        this.state = {
            dataSource: dataSource.cloneWithRows(props.categories)
        }
    }

    render() {
        return (
            <View style={styles.categoryList}>
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow}
                    enableEmptySections={true}
                    activeOpacity={1}
                    onPress={()=>this.handleSortTypesViewAnimation()}
                />
            </View>
        )
    }

    _renderRow(category, sectionId, rowId) {
        return (
            <View>
                <Text>{category.name}</Text>
            </View>
        );
    }
}

class ProductList extends React.Component {
    constructor(props) {
        super(props);
        // Alert.alert(this.props.categories);

        this._renderRow = this._renderRow.bind(this);

        let dataSource = new ListView.DataSource({
            getRowData: (data, sectionId, rowId) => {
                return data[sectionId][rowId];
            },
            getSectionHeaderData: (data, sectionId) => {
                return data[sectionId];
            },
            rowHasChanged: (row1, row2) => row1 !== row2,
            sectionHeaderHasChanged: (section1, section2) => section1 !== section2,
        });

        this.state = {
            dataSource: dataSource.cloneWithRows(props.categories)
        }
    }

    render() {
        return (
            <View style={styles.productList}>
                <ListView style={styles.productList}
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow}
                    enableEmptySections={true}
                />
            </View>
        )
    }

    _renderRow(category, sectionId, rowId) {
        return (
            <View>
                <Text>{category.name}</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        flexDirection:'row',
    },
    categoryList: {
        backgroundColor: '#88f0f3',
        width: 80,
    },
    productList: {
        backgroundColor: '#aaaaf3',
        flex: 1,
    },
    category:{
        justifyContent: 'center',
        alignItems: 'center',
        height:50,
    },
    category_bg_select:{
        backgroundColor:'#d7ead6',
    },
    category_bg_normal:{
        backgroundColor:'#ffffff',
    },
    line:{
        backgroundColor:'#eef0f3',
        height:1,
    },
})<commit_msg>TabView-分类<commit_after>/**
 * ShopReactNative
 *
 * @author Tony Wong
 * @date 2016-08-13
 * @email 908601756@qq.com
 * @copyright Copyright © 2016 EleTeam
 * @license The MIT License (MIT)
 */

'use strict';

import React from 'react';
import {
    StyleSheet,
    View,
    ListView,
    Text,
    InteractionManager,
    TouchableOpacity,
    Alert
} from 'react-native';
import {categoryListWithProduct} from '../actions/productActions';
import Loading from '../components/Loading';

//页面变量
let isLoading = true;
let products = [];

export default class CategoryPage extends React.Component {
    constructor(props) {
        super(props);
    }

    componentDidMount() {
        //交互管理器在任意交互/动画完成之后，允许安排长期的运行工作. 在所有交互都完成之后安排一个函数来运行。
        InteractionManager.runAfterInteractions(() => {
            const {dispatch} = this.props;
            dispatch(categoryListWithProduct(isLoading));
        });
    }

    render() {
        const {productReducer} = this.props;
        let categories = productReducer.categories;
        // alert(productReducer.isLoading);
        if (!productReducer.isLoading){
            products = categories[0].products;
            // Alert.alert(products);
        }

        return (
            <View style={styles.container}>
                {productReducer.isLoading ?
                    <Loading /> :
                    <View style={styles.container}>
                        <CategoryList categories={categories}/>
                        <ProductList/>
                    </View>
                }
            </View>
        )
    }
}

class CategoryList extends React.Component {
    constructor(props) {
        super(props);

        this._renderRow = this._renderRow.bind(this);

        let dataSource = new ListView.DataSource({
            getRowData: (data, sectionId, rowId) => {
                return data[sectionId][rowId];
            },
            getSectionHeaderData: (data, sectionId) => {
                return data[sectionId];
            },
            rowHasChanged: (row1, row2) => row1 !== row2,
            sectionHeaderHasChanged: (section1, section2) => section1 !== section2,
        });

        this.state = {
            dataSource: dataSource.cloneWithRows(props.categories)
        }
    }

    render() {
        return (
            <View style={styles.categoryList}>
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow}
                    enableEmptySections={true}
                    activeOpacity={1}
                    onPress={()=>this.handleSortTypesViewAnimation()}
                />
            </View>
        )
    }

    _renderRow(category, sectionId, rowId) {
        return (
            <TouchableOpacity
                style={styles.foodsCell}
                onPress={this._onPressCategoryItem.bind(this, rowId)}
            >
                <View style={styles.categoryItem}>
                    <Text>{category.name}</Text>
                </View>
            </TouchableOpacity>
        );
    }

    _onPressCategoryItem(rowId) {
        products = this.props.categories[rowId];
        // Alert.alert(products);
    }
}

class ProductList extends React.Component {
    constructor(props) {
        super(props);

        this._renderRow = this._renderRow.bind(this);

        let dataSource = new ListView.DataSource({
            getRowData: (data, sectionId, rowId) => {
                return data[sectionId][rowId];
            },
            getSectionHeaderData: (data, sectionId) => {
                return data[sectionId];
            },
            rowHasChanged: (row1, row2) => row1 !== row2,
            sectionHeaderHasChanged: (section1, section2) => section1 !== section2,
        });

        this.state = {
            dataSource: dataSource.cloneWithRows(products)
        }
    }

    render() {
        return (
            <View style={styles.productList}>
                <ListView style={styles.productList}
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow}
                    enableEmptySections={true}
                />
            </View>
        )
    }

    _renderRow(product, sectionId, rowId) {
        return (
            <View>
                <Text>{product.name}</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        flexDirection:'row',
    },
    categoryList: {
        backgroundColor: '#88f0f3',
        width: 70,
    },
    productList: {
        backgroundColor: '#aaaaf3',
        flex: 1,
    },

    categoryItem:{
        alignItems: 'center',    //水平居中
        justifyContent: 'center',//垂直居中
        height:50,
    },
    category_bg_select:{
        backgroundColor:'#d7ead6',
    },
    category_bg_normal:{
        backgroundColor:'#ffffff',
    },
    line:{
        backgroundColor:'#eef0f3',
        height:1,
    },
});<|endoftext|>


<commit_before>import React, { Component } from "react";
import { connect } from "react-redux";

import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery
} from "@appbaseio/reactivecore/lib/actions";
import {
	checkValueChange,
	checkPropChange
} from "@appbaseio/reactivecore/lib/utils/helper";
import types from "@appbaseio/reactivecore/lib/utils/types";

import Title from "../../styles/Title";
import Button, { numberBoxButtons } from "../../styles/Button";
import Flex from "../../styles/Flex";

class NumberBox extends Component {
	constructor(props) {
		super(props);

		this.type = "term";
		this.state = {
			currentValue: this.props.data.start
		};
	}

	componentDidMount() {
		this.props.addComponent(this.props.componentId);
		this.setReact(this.props);

		if (this.props.defaultSelected) {
			this.setValue(this.props.defaultSelected);
		}
	}

	componentWillReceiveProps(nextProps) {
		checkPropChange(this.props.react, nextProps.react, () =>
			this.setReact(nextProps)
		);
		checkPropChange(this.props.defaultSelected, nextProps.defaultSelected, () =>
			this.setValue(nextProps.defaultSelected, nextProps)
		);
		checkPropChange(this.props.queryFormat, nextProps.queryFormat, () =>
			this.updateQuery(this.state.currentValue, nextProps)
		);
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);
	}

	defaultQuery = (value, props) => {
		switch (props.queryFormat) {
			case "exact":
				return {
					term: {
						[props.dataField]: value
					}
				};
			case "lte":
				return {
					range: {
						[props.dataField]: {
							lte: value,
							boost: 2.0
						}
					}
				};
			case "gte":
				return {
					range: {
						[props.dataField]: {
							gte: value,
							boost: 2.0
						}
					}
				};
			default:
				return null;
		}
	};

	setReact(props) {
		if (props.react) {
			props.watchComponent(props.componentId, props.react);
		}
	}

	incrementValue = () => {
		if (this.state.currentValue === this.props.data.end) {
			return;
		}
		const { currentValue } = this.state;
		this.setValue(currentValue + 1);
	};

	decrementValue = () => {
		if (this.state.currentValue === this.props.data.start) {
			return;
		}
		const { currentValue } = this.state;
		this.setValue(currentValue - 1);
	};

	setValue = (value, props = this.props) => {
		const performUpdate = () => {
			this.setState({
				currentValue: value
			});
			this.updateQuery(value, props);
		};
		checkValueChange(
			props.componentId,
			value,
			props.beforeValueChange,
			props.onValueChange,
			performUpdate
		);
	};

	updateQuery = (value, props) => {
		const query = props.customQuery || this.defaultQuery;
		let onQueryChange = null;
		if (props.onQueryChange) {
			onQueryChange = props.onQueryChange;
		}
		props.updateQuery({
			componentId: props.componentId,
			query: query(value, props),
			value,
			onQueryChange,
			showFilter: false,	// we don't need filters for NumberBox
			URLParams: props.URLParams
		});
	};

	render() {
		return (

				{this.props.title && <Title>{this.props.title}</Title>}
				<Flex labelPosition={this.props.labelPosition} inline>
					{this.props.data.label}

						<Button
							onClick={this.decrementValue}
							disabled={this.state.currentValue === this.props.data.start}
						>
							-
						</Button>
						{this.state.currentValue}
						<Button
							onClick={this.incrementValue}
							disabled={this.state.currentValue === this.props.data.end}
						>
							+
						</Button>

				</Flex>

		);
	}
}

NumberBox.propTypes = {
	addComponent: types.addComponent,
	componentId: types.componentId,
	defaultSelected: types.number,
	data: types.dataNumberBox,
	react: types.react,
	removeComponent: types.removeComponent,
	title: types.title,
	queryFormat: types.queryFormatNumberBox,
	labelPosition: types.labelPosition
};

NumberBox.defaultProps = {
	queryFormat: "gte",
	labelPosition: "left"
};

const mapDispatchtoProps = (dispatch, props) => ({
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	watchComponent: (component, react) =>
		dispatch(watchComponent(component, react)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject))
});

export default connect(null, mapDispatchtoProps)(NumberBox);
<commit_msg>Fix NumberBox behavior<commit_after>import React, { Component } from "react";
import { connect } from "react-redux";

import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery
} from "@appbaseio/reactivecore/lib/actions";
import {
	checkValueChange,
	checkPropChange
} from "@appbaseio/reactivecore/lib/utils/helper";
import types from "@appbaseio/reactivecore/lib/utils/types";

import Title from "../../styles/Title";
import Button, { numberBoxButtons } from "../../styles/Button";
import Flex from "../../styles/Flex";

class NumberBox extends Component {
	constructor(props) {
		super(props);

		this.type = "term";
		this.state = {
			currentValue: this.props.data.start
		};
	}

	componentWillMount() {
		this.props.addComponent(this.props.componentId);
		this.setReact(this.props);

		if (this.props.defaultSelected) {
			this.setValue(this.props.defaultSelected);
		}
	}

	componentWillReceiveProps(nextProps) {
		checkPropChange(this.props.react, nextProps.react, () =>
			this.setReact(nextProps)
		);
		checkPropChange(this.props.defaultSelected, nextProps.defaultSelected, () =>
			this.setValue(nextProps.defaultSelected, nextProps)
		);
		checkPropChange(this.props.queryFormat, nextProps.queryFormat, () =>
			this.updateQuery(this.state.currentValue, nextProps)
		);
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);
	}

	defaultQuery = (value, props) => {
		switch (props.queryFormat) {
			case "exact":
				return {
					term: {
						[props.dataField]: value
					}
				};
			case "lte":
				return {
					range: {
						[props.dataField]: {
							lte: value,
							boost: 2.0
						}
					}
				};
			case "gte":
				return {
					range: {
						[props.dataField]: {
							gte: value,
							boost: 2.0
						}
					}
				};
			default:
				return null;
		}
	};

	setReact(props) {
		if (props.react) {
			props.watchComponent(props.componentId, props.react);
		}
	}

	incrementValue = () => {
		if (this.state.currentValue === this.props.data.end) {
			return;
		}
		const { currentValue } = this.state;
		this.setValue(currentValue + 1);
	};

	decrementValue = () => {
		if (this.state.currentValue === this.props.data.start) {
			return;
		}
		const { currentValue } = this.state;
		this.setValue(currentValue - 1);
	};

	setValue = (value, props = this.props) => {
		const performUpdate = () => {
			this.setState({
				currentValue: value
			}, () => {
				const query = props.customQuery || this.defaultQuery;
				this.updateQuery(value, props);
			});
		};
		checkValueChange(
			props.componentId,
			value,
			props.beforeValueChange,
			props.onValueChange,
			performUpdate
		);
	};

	updateQuery = (value, props) => {
		const query = props.customQuery || this.defaultQuery;
		let onQueryChange = null;
		if (props.onQueryChange) {
			onQueryChange = props.onQueryChange;
		}
		props.updateQuery({
			componentId: props.componentId,
			query: query(value, props),
			value,
			onQueryChange,
			showFilter: false,	// we don't need filters for NumberBox
			URLParams: props.URLParams
		});
	};

	render() {
		return (

				{this.props.title && <Title>{this.props.title}</Title>}
				<Flex labelPosition={this.props.labelPosition} inline>
					{this.props.data.label}

						<Button
							onClick={this.decrementValue}
							disabled={this.state.currentValue === this.props.data.start}
						>
							-
						</Button>
						{this.state.currentValue}
						<Button
							onClick={this.incrementValue}
							disabled={this.state.currentValue === this.props.data.end}
						>
							+
						</Button>

				</Flex>

		);
	}
}

NumberBox.propTypes = {
	addComponent: types.addComponent,
	componentId: types.componentId,
	defaultSelected: types.number,
	data: types.dataNumberBox,
	react: types.react,
	removeComponent: types.removeComponent,
	title: types.title,
	queryFormat: types.queryFormatNumberBox,
	labelPosition: types.labelPosition
};

NumberBox.defaultProps = {
	queryFormat: "gte",
	labelPosition: "left"
};

const mapDispatchtoProps = (dispatch, props) => ({
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	watchComponent: (component, react) =>
		dispatch(watchComponent(component, react)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject))
});

export default connect(null, mapDispatchtoProps)(NumberBox);
<|endoftext|>


<commit_before>// Copyright 2002-2015, University of Colorado Boulder
/**
 * @fileoverview Rule to guarantee we do not use Math.sign which is not supported on IE
 * @author Sam Reid (PhET Interactive Simulations)
 * @copyright 2017 University of Colorado Boulder
 */

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function( context ) {
  'use strict';

  return {

    MemberExpression: function noMathSign( node ) {

      // See https://astexplorer.net/ for an interactive tool that lets you explore ASTs
      if ( node.object && node.object.name === 'Math' && node.property && node.property.name === 'sign' ) {
        context.report( {
          node: node,
          loc: node.loc.start,
          message: 'Math.sign is not supported on IE'
        } );
      }
    }
  };
};

module.exports.schema = [
  // JSON Schema for rule options goes here
];<commit_msg>Added "please use Util.sign" to error report, see https://github.com/phetsims/circuit-construction-kit-dc/issues/163<commit_after>// Copyright 2002-2015, University of Colorado Boulder
/**
 * @fileoverview Rule to guarantee we do not use Math.sign which is not supported on IE
 * @author Sam Reid (PhET Interactive Simulations)
 * @copyright 2017 University of Colorado Boulder
 */

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function( context ) {
  'use strict';

  return {

    MemberExpression: function noMathSign( node ) {

      // See https://astexplorer.net/ for an interactive tool that lets you explore ASTs
      if ( node.object && node.object.name === 'Math' && node.property && node.property.name === 'sign' ) {
        context.report( {
          node: node,
          loc: node.loc.start,
          message: 'Math.sign is not supported on IE, please use DOT/Util.sign'
        } );
      }
    }
  };
};

module.exports.schema = [
  // JSON Schema for rule options goes here
];<|endoftext|>


<commit_before>import Config from 'react-native-config';
import GoogleOAuth from 'config/oAuth/GoogleOAuth';

// Actions
const GET_USER_REQUEST = 'user/GET_USER_REQUEST';
const GET_USER_COMMIT = 'user/GET_USER_COMMIT';
const SET_LOGIN_STATUS = 'user/SET_LOGIN_STATUS';
export const LOGOUT_REQUEST = 'user/LOGOUT_REQUEST';
const LOGOUT_COMMIT = 'user/LOGOUT_COMMIT';

// Reducer
const initialState = {
  data: {},
  loggedIn: false,
  token: null,
  socialNetwork: null,
  logoutSuccess: true,
  synced: false,
  syncing: false
};

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case GET_USER_REQUEST:
      return { ...state, synced: false, syncing: true };
    case GET_USER_COMMIT: {
      const user = action.payload;
      return { ...state, data: user, synced: true, syncing: false };
    }
    case SET_LOGIN_STATUS:
      return Object.assign({}, state, { ...action.payload });
    case LOGOUT_REQUEST:
      return { ...initialState, logoutSuccess: false };
    case LOGOUT_COMMIT:
      return { ...initialState, logoutSuccess: true };
    default:
      return state;
  }
}

// Action Creators
export function getUser() {
  return {
    type: GET_USER_REQUEST,
    meta: {
      offline: {
        effect: { url: `${Config.API_AUTH}/user` },
        commit: { type: GET_USER_COMMIT }
      }
    }
  };
}

export function setLoginStatus(status) {
  return {
    type: SET_LOGIN_STATUS,
    payload: status
  };
}

export function loginGoogle() {
  return (dispatch) => {
    GoogleOAuth.login()
    .then((user) => {
      fetch(`${Config.API_AUTH}/auth/google/token?access_token=${user.accessToken}`)
        .then(response => {
          if (response.ok) return response.json();
          throw new Error(response.statusText);
        })
        .then(data => dispatch({
          type: SET_LOGIN_STATUS,
          payload: {
            socialNetwork: 'google',
            loggedIn: true,
            token: data.token
          }
        }))
        .catch(() => {
          GoogleOAuth.logout()
            .then(GoogleOAuth.reset);
        });
    });
  };
}

export function logout() {
  return (dispatch, state) => {
    if (state().user.socialNetwork === 'google') {
      return dispatch({
        type: LOGOUT_REQUEST,
        meta: {
          offline: {
            effect: { promise: GoogleOAuth.logout(), errorCode: 500 },
            commit: { type: LOGOUT_COMMIT }
          }
        }
      });
    }
    return dispatch({
      type: LOGOUT_COMMIT
    });
  };
}
<commit_msg>Clean cookies on logout<commit_after>import Config from 'react-native-config';
import GoogleOAuth from 'config/oAuth/GoogleOAuth';

const CookieManager = require('react-native-cookies');

// Actions
const GET_USER_REQUEST = 'user/GET_USER_REQUEST';
const GET_USER_COMMIT = 'user/GET_USER_COMMIT';
const SET_LOGIN_STATUS = 'user/SET_LOGIN_STATUS';
export const LOGOUT_REQUEST = 'user/LOGOUT_REQUEST';
const LOGOUT_COMMIT = 'user/LOGOUT_COMMIT';

// Reducer
const initialState = {
  data: {},
  loggedIn: false,
  token: null,
  socialNetwork: null,
  logoutSuccess: true,
  synced: false,
  syncing: false
};

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case GET_USER_REQUEST:
      return { ...state, synced: false, syncing: true };
    case GET_USER_COMMIT: {
      const user = action.payload;
      return { ...state, data: user, synced: true, syncing: false };
    }
    case SET_LOGIN_STATUS:
      return Object.assign({}, state, { ...action.payload });
    case LOGOUT_REQUEST:
      return { ...initialState, logoutSuccess: false };
    case LOGOUT_COMMIT:
      return { ...initialState, logoutSuccess: true };
    default:
      return state;
  }
}

// Action Creators
export function getUser() {
  return {
    type: GET_USER_REQUEST,
    meta: {
      offline: {
        effect: { url: `${Config.API_AUTH}/user` },
        commit: { type: GET_USER_COMMIT }
      }
    }
  };
}

export function setLoginStatus(status) {
  return {
    type: SET_LOGIN_STATUS,
    payload: status
  };
}

export function loginGoogle() {
  return (dispatch) => {
    GoogleOAuth.login()
    .then((user) => {
      fetch(`${Config.API_AUTH}/auth/google/token?access_token=${user.accessToken}`)
        .then(response => {
          if (response.ok) return response.json();
          throw new Error(response.statusText);
        })
        .then(data => dispatch({
          type: SET_LOGIN_STATUS,
          payload: {
            socialNetwork: 'google',
            loggedIn: true,
            token: data.token
          }
        }))
        .catch(() => {
          GoogleOAuth.logout()
            .then(GoogleOAuth.reset);
        });
    });
  };
}

export function logout() {
  return (dispatch, state) => {
    CookieManager.clearAll(() => {
      if (state().user.socialNetwork === 'google') {
        return dispatch({
          type: LOGOUT_REQUEST,
          meta: {
            offline: {
              effect: { promise: GoogleOAuth.logout(), errorCode: 500 },
              commit: { type: LOGOUT_COMMIT }
            }
          }
        });
      }
      return dispatch({
        type: LOGOUT_COMMIT
      });
    });
  };
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import AjaxPromise from '../utils/ajax-promise';
import config from '../config/environment';
import preloadDataMixin from '../mixins/preload_data';
const { getOwner } = Ember;

export default Ember.Route.extend(preloadDataMixin, {
  cordova: Ember.inject.service(),
  i18n: Ember.inject.service(),
  isErrPopUpAlreadyShown: false,
  isOfflineErrAlreadyShown: false,
  logger: Ember.inject.service(),
  messageBox: Ember.inject.service(),
  isMustLoginAlreadyShown: false,
  isalreadyLoggedinShown: false,

  _loadDataStore: function(){
    return this.preloadData(true).catch(error => {
      if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
        this.transitionTo("offline");
      } else {
        this.handleError(error);
      }
    }).finally(() => {
      // don't know why but placing this before preloadData on iPhone 6 causes register_device request to fail with status 0
      if (this.session.get('isLoggedIn')) {
        this.get("cordova").appLoad();
      }
    });
  },

  init() {
    var _this = this;
    var storageHandler = function (object) {
      var currentPath = window.location.href;
      var authToken = window.localStorage.getItem('authToken');
      if(!authToken && !object.get('isMustLoginAlreadyShown') && !(currentPath.includes("login") || currentPath.includes("authenticate"))) {
        object.set('isMustLoginAlreadyShown', true);
        object.store.unloadAll('user_profile');
        object.get('messageBox').alert(object.get("i18n").t('must_login'), () => {
          window.location.reload();
        });
      } else if(!object.get("isalreadyLoggedinShown") authToken && !currentPath.includes("offer") &&(currentPath.includes("login") || currentPath.includes("authenticate"))) {
        object.set("isalreadyLoggedinShown", true);
        object.get('messageBox').alert("Logged in from another window, press ok to refresh.", () => {
          window.location.reload();
        });
      }
    };
    window.addEventListener("storage", function() {
      storageHandler(_this);
    }, false);
  },

  beforeModel(transition = []) {
    var language;
    try {
      window.localStorage.test = "isSafariPrivateBrowser";
    } catch (e) {
      this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
    }
    window.localStorage.removeItem('test');
    if (transition.queryParams.ln) {
      language = transition.queryParams.ln === "zh-tw" ? "zh-tw" : "en";
      this.set('session.language', language);
    }

    language = this.session.get("language") || "en";
    moment.locale(language);
    this.set("i18n.locale", language);

    Ember.onerror = window.onerror = error => this.handleError(error);
    return this._loadDataStore();
  },

  afterModel() {
    if(this.get("session.isAdminApp")) {
      this.loadStaticData(true).catch(error => {
        if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
          this.transitionTo("offline");
        } else {
          this.handleError(error);
        }
      });
    }
  },

  renderTemplate() {
    this.render(); // default template

    this.render('notifications', {   // the template to render
      into: 'application',      // the template to render into
      outlet: 'notifications', // the name of the outlet in that template
      controller: 'notifications'   // the controller to use for the template
    });

    if(this.get("session.isAdminApp")){
      this.render('notification_link', {
        into: 'application',
        outlet: 'notification_link',
        controller: 'notification_link'
      });

      this.render('internet_call_status', {
        into: 'application',
        outlet: 'internet_call_status',
        controller: 'internet_call_status'
      });
    }
  },

  offlineError(reason){
    if(!this.get('isOfflineErrAlreadyShown')) {
      this.set('isOfflineErrAlreadyShown', true);
      this.get("messageBox").alert(this.get("i18n").t("offline_error"), () => {
        this.set('isOfflineErrAlreadyShown', false);
      });
      if(!reason.isAdapterError){
        this.get("logger").error(reason);
      }
    }
  },

  quotaExceededError(reason){
    this.get("logger").error(reason);
    this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
  },

  somethingWentWrong(reason) {
    this.get("logger").error(reason);
    if(!this.get('isErrPopUpAlreadyShown')) {
      this.set('isErrPopUpAlreadyShown', true);
      this.get("messageBox").alert(this.get("i18n").t("unexpected_error"), () => {
        this.set('isErrPopUpAlreadyShown', false);
      });
    }
  },

  notFoundError(reason) {
    this.get("logger").error(reason);
    this.get("messageBox").alert(this.get("i18n").t(status+"_error"));
  },

  unauthorizedError() {
    if (this.session.get('isLoggedIn')) {
      this.controllerFor("application").send('logMeOut');
    }
  },

  handleError: function(reason) {
    try
    {
      var status;
      try { status = parseInt(reason.errors[0].status, 10); }
      catch (err) { status = reason.status; }

      if(!window.navigator.onLine){
        this.offlineError(reason);
      } else if(reason.name === "QuotaExceededError") {
        this.quotaExceededError(reason);
      } else if (status === 401) {
        this.unauthorizedError();
      } else if ([403, 404].indexOf(status) >= 0) {
        this.notFoundError(reason);
      } else if (status === 0) {
        // status 0 means request was aborted, this could be due to connection failure
        // but can also mean request was manually cancelled
        this.get("messageBox").alert(this.get("i18n").t("offline_error"));
      } else {
        this.somethingWentWrong(reason);
      }
    } catch (err) {
      console.log(err);
    }
  },

  actions: {
    setLang(language) {
      this.session.set("language", language);
      window.location.reload();
    },
    loading() {
      Ember.$(".loading-indicator").remove();
      var view = getOwner(this).lookup('component:loading').append();
      this.router.one('didTransition', view, 'destroy');
    },
    // this is hopefully only triggered from promises from routes
    // so in this scenario redirect to home for 404
    error(reason) {
      try {
        var errorStatus = parseInt(reason.status || reason.errors && reason.errors[0].status, 10)
        if ([403, 404].indexOf(errorStatus) >= 0) {
          this.get("messageBox").alert(this.get("i18n").t(errorStatus+"_error"), () => this.transitionTo("/"));
        } else {
          this.handleError(reason);
        }
      } catch (err) {
        console.log(err);
      }
    },

    willTransition(transition) {
      Ember.run.next(function() {
        // before transitioning close all foundation-dialog box
        Ember.$(".reveal-modal").foundation("reveal", "close");

        // remove joyride-popup if not assigned for page
        if($(".joyride-list").length === 0) {
          Ember.$('.joyride-tip-guide').remove();
        }
      });
    }
  }
});
<commit_msg>CI build fixed<commit_after>import Ember from 'ember';
import AjaxPromise from '../utils/ajax-promise';
import config from '../config/environment';
import preloadDataMixin from '../mixins/preload_data';
const { getOwner } = Ember;

export default Ember.Route.extend(preloadDataMixin, {
  cordova: Ember.inject.service(),
  i18n: Ember.inject.service(),
  isErrPopUpAlreadyShown: false,
  isOfflineErrAlreadyShown: false,
  logger: Ember.inject.service(),
  messageBox: Ember.inject.service(),
  isMustLoginAlreadyShown: false,
  isalreadyLoggedinShown: false,

  _loadDataStore: function(){
    return this.preloadData(true).catch(error => {
      if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
        this.transitionTo("offline");
      } else {
        this.handleError(error);
      }
    }).finally(() => {
      // don't know why but placing this before preloadData on iPhone 6 causes register_device request to fail with status 0
      if (this.session.get('isLoggedIn')) {
        this.get("cordova").appLoad();
      }
    });
  },

  init() {
    var _this = this;
    var storageHandler = function (object) {
      var currentPath = window.location.href;
      var authToken = window.localStorage.getItem('authToken');
      if(!authToken && !object.get('isMustLoginAlreadyShown') && !(currentPath.includes("login") || currentPath.includes("authenticate"))) {
        object.set('isMustLoginAlreadyShown', true);
        object.store.unloadAll('user_profile');
        object.get('messageBox').alert(object.get("i18n").t('must_login'), () => {
          window.location.reload();
        });
      } else if(!object.get("isalreadyLoggedinShown") && authToken && !currentPath.includes("offer") &&(currentPath.includes("login") || currentPath.includes("authenticate"))) {
        object.set("isalreadyLoggedinShown", true);
        object.get('messageBox').alert("Logged in from another window, press ok to refresh.", () => {
          window.location.reload();
        });
      }
    };
    window.addEventListener("storage", function() {
      storageHandler(_this);
    }, false);
  },

  beforeModel(transition = []) {
    var language;
    try {
      window.localStorage.test = "isSafariPrivateBrowser";
    } catch (e) {
      this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
    }
    window.localStorage.removeItem('test');
    if (transition.queryParams.ln) {
      language = transition.queryParams.ln === "zh-tw" ? "zh-tw" : "en";
      this.set('session.language', language);
    }

    language = this.session.get("language") || "en";
    moment.locale(language);
    this.set("i18n.locale", language);

    Ember.onerror = window.onerror = error => this.handleError(error);
    return this._loadDataStore();
  },

  afterModel() {
    if(this.get("session.isAdminApp")) {
      this.loadStaticData(true).catch(error => {
        if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
          this.transitionTo("offline");
        } else {
          this.handleError(error);
        }
      });
    }
  },

  renderTemplate() {
    this.render(); // default template

    this.render('notifications', {   // the template to render
      into: 'application',      // the template to render into
      outlet: 'notifications', // the name of the outlet in that template
      controller: 'notifications'   // the controller to use for the template
    });

    if(this.get("session.isAdminApp")){
      this.render('notification_link', {
        into: 'application',
        outlet: 'notification_link',
        controller: 'notification_link'
      });

      this.render('internet_call_status', {
        into: 'application',
        outlet: 'internet_call_status',
        controller: 'internet_call_status'
      });
    }
  },

  offlineError(reason){
    if(!this.get('isOfflineErrAlreadyShown')) {
      this.set('isOfflineErrAlreadyShown', true);
      this.get("messageBox").alert(this.get("i18n").t("offline_error"), () => {
        this.set('isOfflineErrAlreadyShown', false);
      });
      if(!reason.isAdapterError){
        this.get("logger").error(reason);
      }
    }
  },

  quotaExceededError(reason){
    this.get("logger").error(reason);
    this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
  },

  somethingWentWrong(reason) {
    this.get("logger").error(reason);
    if(!this.get('isErrPopUpAlreadyShown')) {
      this.set('isErrPopUpAlreadyShown', true);
      this.get("messageBox").alert(this.get("i18n").t("unexpected_error"), () => {
        this.set('isErrPopUpAlreadyShown', false);
      });
    }
  },

  notFoundError(reason) {
    this.get("logger").error(reason);
    this.get("messageBox").alert(this.get("i18n").t(status+"_error"));
  },

  unauthorizedError() {
    if (this.session.get('isLoggedIn')) {
      this.controllerFor("application").send('logMeOut');
    }
  },

  handleError: function(reason) {
    try
    {
      var status;
      try { status = parseInt(reason.errors[0].status, 10); }
      catch (err) { status = reason.status; }

      if(!window.navigator.onLine){
        this.offlineError(reason);
      } else if(reason.name === "QuotaExceededError") {
        this.quotaExceededError(reason);
      } else if (status === 401) {
        this.unauthorizedError();
      } else if ([403, 404].indexOf(status) >= 0) {
        this.notFoundError(reason);
      } else if (status === 0) {
        // status 0 means request was aborted, this could be due to connection failure
        // but can also mean request was manually cancelled
        this.get("messageBox").alert(this.get("i18n").t("offline_error"));
      } else {
        this.somethingWentWrong(reason);
      }
    } catch (err) {
      console.log(err);
    }
  },

  actions: {
    setLang(language) {
      this.session.set("language", language);
      window.location.reload();
    },
    loading() {
      Ember.$(".loading-indicator").remove();
      var view = getOwner(this).lookup('component:loading').append();
      this.router.one('didTransition', view, 'destroy');
    },
    // this is hopefully only triggered from promises from routes
    // so in this scenario redirect to home for 404
    error(reason) {
      try {
        var errorStatus = parseInt(reason.status || reason.errors && reason.errors[0].status, 10)
        if ([403, 404].indexOf(errorStatus) >= 0) {
          this.get("messageBox").alert(this.get("i18n").t(errorStatus+"_error"), () => this.transitionTo("/"));
        } else {
          this.handleError(reason);
        }
      } catch (err) {
        console.log(err);
      }
    },

    willTransition(transition) {
      Ember.run.next(function() {
        // before transitioning close all foundation-dialog box
        Ember.$(".reveal-modal").foundation("reveal", "close");

        // remove joyride-popup if not assigned for page
        if($(".joyride-list").length === 0) {
          Ember.$('.joyride-tip-guide').remove();
        }
      });
    }
  }
});
<|endoftext|>


<commit_before>var whatIsThis = (function(){
  var whatIsThis = {};

  var infoEl;
  var teaserEl;
  var shieldEl;

  var LOCAL_STORAGE_NAME = 'what-is-this-hidden-';

  var INFO_DELAY = 500;

  var SHIELD_OPACITY_DELAY = 200;

  function _addCssCode(cssCode) {
    var styleElement = document.createElement('style');
    styleElement.type = 'text/css';
    if (styleElement.styleSheet) {
      styleElement.styleSheet.cssText = cssCode;
    } else {
      styleElement.appendChild(document.createTextNode(cssCode));
    }
    document.querySelector('head').appendChild(styleElement);
  };

  function _addCss() {
    var cssCode;

    cssCode = [];
    cssCode.push('#what-is-this-info,');
    cssCode.push('#what-is-this-teaser {');
    cssCode.push('  position: absolute;');
    cssCode.push('  z-index: 9999999999;');
    cssCode.push('  font-family: "Open Sans", Arial, sans-serif;');
    cssCode.push('  font-size: 13px;');
    cssCode.push('  line-height: 20px;');
    cssCode.push('  left: 50%;');
    cssCode.push('  margin-left: -250px;');
    cssCode.push('  box-sizing: border-box;');
    cssCode.push('  top: -99999px;');
    cssCode.push('  width: 500px;');
    cssCode.push('  border-radius: 3px;');
    cssCode.push('  padding: 10px 20px 20px 20px;');
    cssCode.push('  overflow: hidden;');
    cssCode.push('  box-shadow: 0 2px 5px rgba(0, 0, 0, .5);');
    cssCode.push('  -webkit-transition: background 250ms;');
    cssCode.push('  -moz-transition: background 250ms;');
    cssCode.push('  transition: background 250ms;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-shield {');
    cssCode.push('  z-index: 999998;');
    cssCode.push('  position: fixed;');
    cssCode.push('  left: 0;');
    cssCode.push('  right: 0;');
    cssCode.push('  top: 0;');
    cssCode.push('  bottom: 0;');
    cssCode.push('  cursor: pointer;');
    cssCode.push('  opacity: 0;');
    cssCode.push('  background: white;');
    cssCode.push('  display: none;');
    cssCode.push('  -webkit-transition: opacity 200ms;');
    cssCode.push('  -moz-transition: opacity 200ms;');
    cssCode.push('  transition: opacity 200ms;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info {');
    cssCode.push('  background: white;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info::after {');
    cssCode.push('  content: "";');
    cssCode.push('  position: absolute;');
    cssCode.push('  right: 25px;');
    cssCode.push('  bottom: -20px;');
    cssCode.push('  display: block;');
    cssCode.push('  width: 24px;');
    cssCode.push('  height: 66px;');
    cssCode.push('  background-size: 24px 66px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-teaser {');
    cssCode.push('  z-index: 100000;');
    cssCode.push('  background: white');
    cssCode.push('  color: black;');
    cssCode.push('  box-shadow: none;');
    cssCode.push('  text-shadow: none;');
    cssCode.push('  box-sizing: border-box;');
    cssCode.push('  width: 170px;');
    cssCode.push('  height: 45px;');
    cssCode.push('  line-height: 46px;');
    cssCode.push('  left: -13px;');
    cssCode.push('  margin-left: 0;');
    cssCode.push('  top: -50px;');
    cssCode.push('  padding: 0;');
    cssCode.push('  padding-left: 8px;');
    cssCode.push('  letter-spacing: 6px;');
    cssCode.push('  text-transform: uppercase;');
    cssCode.push('  font-weight: bold;');
    cssCode.push('  -webkit-user-select: none;');
    cssCode.push('  user-select: none;');
    cssCode.push('  text-align: center;');
    cssCode.push('  position: absolute;');
    cssCode.push('  white-space: nowrap;');
    cssCode.push('  overflow: hidden;');
    cssCode.push('  cursor: pointer;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-teaser:hover {');
    cssCode.push('  color: rgb(64, 64, 64);');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    /*cssCode = [];
    cssCode.push('#what-is-this-teaser:active {');
    cssCode.push('  box-shadow: 0 2px 2px rgba(0, 0, 0, .5);');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));*/

    cssCode = [];
    cssCode.push('#what-is-this-teaser::after {');
    cssCode.push('  content: "";');
    cssCode.push('  position: absolute;');
    cssCode.push('  right: 8px;');
    cssCode.push('  bottom: -40px;');
    cssCode.push('  display: block;');
    cssCode.push('  width: 24px;');
    cssCode.push('  height: 66px;');
    cssCode.push('  background-size: 24px 66px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info p,');
    cssCode.push('#what-is-this-info h1 {');
    cssCode.push('  margin: 10px 0;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info h1 {');
    cssCode.push('  font-size: 100%;');
    cssCode.push('  font-weight: 600;');
    cssCode.push('  margin-bottom: 20px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info a {');
    cssCode.push('  color: rgb(50, 100, 200);');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info a:active {');
    cssCode.push('  position: relative;');
    cssCode.push('  top: 1px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info .close {');
    cssCode.push('  position: absolute;');
    cssCode.push('  bottom: 20px;');
    cssCode.push('  right: 20px;');
    cssCode.push('  z-index: 1000;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info ul {');
    cssCode.push('  list-style: none;');
    cssCode.push('  margin: 0;');
    cssCode.push('  padding: 0;');
    cssCode.push('  margin-left: 1em;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info li::before {');
    cssCode.push('  content: "·";');
    cssCode.push('  position: absolute;');
    cssCode.push('  width: 1em;');
    cssCode.push('  margin-left: -1em;');
    cssCode.push('  display: inline-block;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));
  }

  function _addHtml() {
    infoEl = document.createElement('div');

    var contentEl = document.body.querySelector('#what-is-this-content');

    var els = contentEl.querySelectorAll('a');
    for (var i = 0, el; el = els[i]; i++) {
      el.target = '_blank';
    }

    infoEl.innerHTML = contentEl.innerHTML;
    infoEl.innerHTML += '
Close
';
    contentEl.parentNode.removeChild(contentEl);

    infoEl.id = 'what-is-this-info';
    // TODO: Remove from here into CSS
    infoEl.style.opacity = .5;
    document.querySelector('#main-screen').appendChild(infoEl);

    teaserEl = document.createElement('div');
    teaserEl.id = 'what-is-this-teaser';
    teaserEl.innerHTML = 'Streetmix';
    document.querySelector('#top-menu-bar').appendChild(teaserEl);

    shieldEl = document.createElement('div');
    shieldEl.id = 'what-is-this-shield';
    document.querySelector('#main-screen').appendChild(shieldEl);
    shieldEl.addEventListener('click', whatIsThis.hideInfo, false);

    teaserEl.addEventListener('click', _showInfo, false);
  }

  function _showInfo() {
    var height = infoEl.offsetHeight;
    // TODO: Add constant
    infoEl.style.top = -(height + 50) + 'px';

    window.setTimeout(_showInfoPart2, 0);
  }

  function _showInfoPart2() {
    // TODO: Remove from here into CSS
    infoEl.style.webkitTransition = 'opacity 750ms, top 750ms';
    infoEl.style.MozTransition = 'opacity 750ms, top 750ms';
    infoEl.style.top = '-10px';
    infoEl.style.opacity = 1;

    shieldEl.style.display = 'block';
    window.setTimeout(function() { shieldEl.style.opacity = '.5'; }, 0);

    _hideTeaser();
  }

  function _showInfoWithDelay() {
    window.setTimeout(_showInfo, INFO_DELAY);
  }

  whatIsThis.hideInfo = function() {
    // TODO: Remove from here into CSS
    infoEl.style.webkitTransition = 'opacity 250ms, top 250ms';
    infoEl.style.MozTransition = 'opacity 250ms, top 250ms';
    var height = infoEl.offsetHeight;
    infoEl.style.top = -(height + 50) + 'px';
    infoEl.style.opacity = .5;

    shieldEl.style.opacity = 0;
    window.setTimeout(function() { shieldEl.style.display = 'none'; }, SHIELD_OPACITY_DELAY);

    _showTeaser();

    return false;
  }

  function _showTeaser() {
    // TODO: Remove from here into CSS
    teaserEl.style.webkitTransition = 'opacity 750ms, top 750ms';
    teaserEl.style.MozTransition = 'opacity 750ms, top 750ms';
    teaserEl.style.top = '-10px';
    teaserEl.style.opacity = 1;
  }

  function _hideTeaser() {
    // TODO: Remove from here into CSS
    teaserEl.style.webkitTransition = 'opacity 250ms, top 250ms';
    teaserEl.style.MozTransition = 'opacity 250ms, top 250ms';
    teaserEl.style.top = '-50px';
    teaserEl.style.opacity = .5;
  }

  function _start() {
    _addCss();
    _addHtml();

    if (webStorageProxy.getItem( (LOCAL_STORAGE_NAME + location.host)) == 'true') {
      _showTeaser();
    } else {
      _showInfoWithDelay();
    }

    webStorageProxy.setItem( (LOCAL_STORAGE_NAME + location.host) , true);
  }

  whatIsThis.init = function() {
    document.addEventListener('DOMContentLoaded', _start, false);
  }

  return whatIsThis;
})();

whatIsThis.init();

// interface for interacting with web storage (local storage and session storage).
// A final parameter of true on all methods specifies to use session storage
// (non-persistant storage). A final parameter of false on all methods specifies to use
// local storage (persistent storage). E.g. webStorageProxy.setItem("name","Anselm",true)
// for session storage or webStorageProxy.getItem("name") for local storage (note omitted
// 'true' value).
// Note that values stored in local storage are not accessible from session storage and
// vice versa. They both work on different objects within the browser.
var webStorageProxy = (function(){
  var webStorageProxy = {};

	// set an item value specified by the key. Returns the value.
	webStorageProxy.setItem = function(key, value, sessionOnly) {
		var storage = _sessionOrLocal(sessionOnly);
		storage.setItem(key,value);
		return value;
	}

	// get an item specified by the key. Returns null if the item has no value.
	webStorageProxy.getItem = function(key, sessionOnly) {
		var storage = _sessionOrLocal(sessionOnly);
		return storage.getItem(key);
	}

	// remove an item specified by the key. Returns true if the item existed and it was
	// removed. Returns false if the item didn't exist to begin with.
	webStorageProxy.removeItem = function(key, sessionOnly) {
		var storage = _sessionOrLocal(sessionOnly);
		var returnVal = true;
		if (!storage.getItem(key)) returnVal = false;
		if (returnVal) storage.removeItem(key);

		return returnVal;
	}

	// internal function for whether to use local or session storage.
	function _sessionOrLocal(sessionOnly)
	{
		var storage;
		(sessionOnly) ? storage = sessionStorage : storage = localStorage;
		return storage;
	}

  return webStorageProxy;
})();
<commit_msg>Remove introduction screen.<commit_after>var whatIsThis = (function(){
  var whatIsThis = {};

  var infoEl;
  var teaserEl;
  var shieldEl;

  var LOCAL_STORAGE_NAME = 'what-is-this-hidden-';

  var INFO_DELAY = 500;

  var SHIELD_OPACITY_DELAY = 200;

  function _addCssCode(cssCode) {
    var styleElement = document.createElement('style');
    styleElement.type = 'text/css';
    if (styleElement.styleSheet) {
      styleElement.styleSheet.cssText = cssCode;
    } else {
      styleElement.appendChild(document.createTextNode(cssCode));
    }
    document.querySelector('head').appendChild(styleElement);
  };

  function _addCss() {
    var cssCode;

    cssCode = [];
    cssCode.push('#what-is-this-info,');
    cssCode.push('#what-is-this-teaser {');
    cssCode.push('  position: absolute;');
    cssCode.push('  z-index: 9999999999;');
    cssCode.push('  font-family: "Open Sans", Arial, sans-serif;');
    cssCode.push('  font-size: 13px;');
    cssCode.push('  line-height: 20px;');
    cssCode.push('  left: 50%;');
    cssCode.push('  margin-left: -250px;');
    cssCode.push('  box-sizing: border-box;');
    cssCode.push('  top: -99999px;');
    cssCode.push('  width: 500px;');
    cssCode.push('  border-radius: 3px;');
    cssCode.push('  padding: 10px 20px 20px 20px;');
    cssCode.push('  overflow: hidden;');
    cssCode.push('  box-shadow: 0 2px 5px rgba(0, 0, 0, .5);');
    cssCode.push('  -webkit-transition: background 250ms;');
    cssCode.push('  -moz-transition: background 250ms;');
    cssCode.push('  transition: background 250ms;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-shield {');
    cssCode.push('  z-index: 999998;');
    cssCode.push('  position: fixed;');
    cssCode.push('  left: 0;');
    cssCode.push('  right: 0;');
    cssCode.push('  top: 0;');
    cssCode.push('  bottom: 0;');
    cssCode.push('  cursor: pointer;');
    cssCode.push('  opacity: 0;');
    cssCode.push('  background: white;');
    cssCode.push('  display: none;');
    cssCode.push('  -webkit-transition: opacity 200ms;');
    cssCode.push('  -moz-transition: opacity 200ms;');
    cssCode.push('  transition: opacity 200ms;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info {');
    cssCode.push('  background: white;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info::after {');
    cssCode.push('  content: "";');
    cssCode.push('  position: absolute;');
    cssCode.push('  right: 25px;');
    cssCode.push('  bottom: -20px;');
    cssCode.push('  display: block;');
    cssCode.push('  width: 24px;');
    cssCode.push('  height: 66px;');
    cssCode.push('  background-size: 24px 66px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-teaser {');
    cssCode.push('  z-index: 100000;');
    cssCode.push('  background: white');
    cssCode.push('  color: black;');
    cssCode.push('  box-shadow: none;');
    cssCode.push('  text-shadow: none;');
    cssCode.push('  box-sizing: border-box;');
    cssCode.push('  width: 170px;');
    cssCode.push('  height: 45px;');
    cssCode.push('  line-height: 46px;');
    cssCode.push('  left: -13px;');
    cssCode.push('  margin-left: 0;');
    cssCode.push('  top: -50px;');
    cssCode.push('  padding: 0;');
    cssCode.push('  padding-left: 8px;');
    cssCode.push('  letter-spacing: 6px;');
    cssCode.push('  text-transform: uppercase;');
    cssCode.push('  font-weight: bold;');
    cssCode.push('  -webkit-user-select: none;');
    cssCode.push('  user-select: none;');
    cssCode.push('  text-align: center;');
    cssCode.push('  position: absolute;');
    cssCode.push('  white-space: nowrap;');
    cssCode.push('  overflow: hidden;');
    cssCode.push('  cursor: pointer;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-teaser:hover {');
    cssCode.push('  color: rgb(64, 64, 64);');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    /*cssCode = [];
    cssCode.push('#what-is-this-teaser:active {');
    cssCode.push('  box-shadow: 0 2px 2px rgba(0, 0, 0, .5);');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));*/

    cssCode = [];
    cssCode.push('#what-is-this-teaser::after {');
    cssCode.push('  content: "";');
    cssCode.push('  position: absolute;');
    cssCode.push('  right: 8px;');
    cssCode.push('  bottom: -40px;');
    cssCode.push('  display: block;');
    cssCode.push('  width: 24px;');
    cssCode.push('  height: 66px;');
    cssCode.push('  background-size: 24px 66px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info p,');
    cssCode.push('#what-is-this-info h1 {');
    cssCode.push('  margin: 10px 0;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info h1 {');
    cssCode.push('  font-size: 100%;');
    cssCode.push('  font-weight: 600;');
    cssCode.push('  margin-bottom: 20px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info a {');
    cssCode.push('  color: rgb(50, 100, 200);');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info a:active {');
    cssCode.push('  position: relative;');
    cssCode.push('  top: 1px;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info .close {');
    cssCode.push('  position: absolute;');
    cssCode.push('  bottom: 20px;');
    cssCode.push('  right: 20px;');
    cssCode.push('  z-index: 1000;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info ul {');
    cssCode.push('  list-style: none;');
    cssCode.push('  margin: 0;');
    cssCode.push('  padding: 0;');
    cssCode.push('  margin-left: 1em;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));

    cssCode = [];
    cssCode.push('#what-is-this-info li::before {');
    cssCode.push('  content: "·";');
    cssCode.push('  position: absolute;');
    cssCode.push('  width: 1em;');
    cssCode.push('  margin-left: -1em;');
    cssCode.push('  display: inline-block;');
    cssCode.push('}');
    _addCssCode(cssCode.join('\n'));
  }

  function _addHtml() {
    infoEl = document.createElement('div');

    var contentEl = document.body.querySelector('#what-is-this-content');

    var els = contentEl.querySelectorAll('a');
    for (var i = 0, el; el = els[i]; i++) {
      el.target = '_blank';
    }

    infoEl.innerHTML = contentEl.innerHTML;
    infoEl.innerHTML += '
Close
';
    contentEl.parentNode.removeChild(contentEl);

    infoEl.id = 'what-is-this-info';
    // TODO: Remove from here into CSS
    infoEl.style.opacity = .5;
    document.querySelector('#main-screen').appendChild(infoEl);

    teaserEl = document.createElement('div');
    teaserEl.id = 'what-is-this-teaser';
    teaserEl.innerHTML = 'Streetmix';
    document.querySelector('#top-menu-bar').appendChild(teaserEl);

    shieldEl = document.createElement('div');
    shieldEl.id = 'what-is-this-shield';
    document.querySelector('#main-screen').appendChild(shieldEl);
    shieldEl.addEventListener('click', whatIsThis.hideInfo, false);

    teaserEl.addEventListener('click', _showInfo, false);
  }

  function _showInfo() {
    var height = infoEl.offsetHeight;
    // TODO: Add constant
    infoEl.style.top = -(height + 50) + 'px';

    window.setTimeout(_showInfoPart2, 0);
  }

  function _showInfoPart2() {
    // TODO: Remove from here into CSS
    infoEl.style.webkitTransition = 'opacity 750ms, top 750ms';
    infoEl.style.MozTransition = 'opacity 750ms, top 750ms';
    infoEl.style.top = '-10px';
    infoEl.style.opacity = 1;

    shieldEl.style.display = 'block';
    window.setTimeout(function() { shieldEl.style.opacity = '.5'; }, 0);

    _hideTeaser();
  }

  function _showInfoWithDelay() {
    window.setTimeout(_showInfo, INFO_DELAY);
  }

  whatIsThis.hideInfo = function() {
    // TODO: Remove from here into CSS
    infoEl.style.webkitTransition = 'opacity 250ms, top 250ms';
    infoEl.style.MozTransition = 'opacity 250ms, top 250ms';
    var height = infoEl.offsetHeight;
    infoEl.style.top = -(height + 50) + 'px';
    infoEl.style.opacity = .5;

    shieldEl.style.opacity = 0;
    window.setTimeout(function() { shieldEl.style.display = 'none'; }, SHIELD_OPACITY_DELAY);

    _showTeaser();

    return false;
  }

  function _showTeaser() {
    // TODO: Remove from here into CSS
    teaserEl.style.webkitTransition = 'opacity 750ms, top 750ms';
    teaserEl.style.MozTransition = 'opacity 750ms, top 750ms';
    teaserEl.style.top = '-10px';
    teaserEl.style.opacity = 1;
  }

  function _hideTeaser() {
    // TODO: Remove from here into CSS
    teaserEl.style.webkitTransition = 'opacity 250ms, top 250ms';
    teaserEl.style.MozTransition = 'opacity 250ms, top 250ms';
    teaserEl.style.top = '-50px';
    teaserEl.style.opacity = .5;
  }

  function _start() {
    _addCss();
    _addHtml();

    /*if (webStorageProxy.getItem( (LOCAL_STORAGE_NAME + location.host)) == 'true') {
      _showTeaser();
    } else {
      _showInfoWithDelay();
    }*/
    _showTeaser();

    webStorageProxy.setItem( (LOCAL_STORAGE_NAME + location.host) , true);
  }

  whatIsThis.init = function() {
    document.addEventListener('DOMContentLoaded', _start, false);
  }

  return whatIsThis;
})();

whatIsThis.init();

// interface for interacting with web storage (local storage and session storage).
// A final parameter of true on all methods specifies to use session storage
// (non-persistant storage). A final parameter of false on all methods specifies to use
// local storage (persistent storage). E.g. webStorageProxy.setItem("name","Anselm",true)
// for session storage or webStorageProxy.getItem("name") for local storage (note omitted
// 'true' value).
// Note that values stored in local storage are not accessible from session storage and
// vice versa. They both work on different objects within the browser.
var webStorageProxy = (function(){
  var webStorageProxy = {};

	// set an item value specified by the key. Returns the value.
	webStorageProxy.setItem = function(key, value, sessionOnly) {
		var storage = _sessionOrLocal(sessionOnly);
		storage.setItem(key,value);
		return value;
	}

	// get an item specified by the key. Returns null if the item has no value.
	webStorageProxy.getItem = function(key, sessionOnly) {
		var storage = _sessionOrLocal(sessionOnly);
		return storage.getItem(key);
	}

	// remove an item specified by the key. Returns true if the item existed and it was
	// removed. Returns false if the item didn't exist to begin with.
	webStorageProxy.removeItem = function(key, sessionOnly) {
    var storage = _sessionOrLocal(sessionOnly);
    var returnVal = true;
    if (!storage.getItem(key)) {
      returnVal = false;
    }
    if (returnVal) {
      storage.removeItem(key);
    }

    return returnVal;
	}

	// internal function for whether to use local or session storage.
	function _sessionOrLocal(sessionOnly)
	{
		var storage;
		(sessionOnly) ? storage = sessionStorage : storage = localStorage;
		return storage;
	}

  return webStorageProxy;
})();
<|endoftext|>