<commit_before>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/
var path = require('path');
var local = path.join.bind(path, __dirname);
var config = require(local('configureIndigo'));
var lib_api = require(local('indigo-api'));
var IndigoObject = require(local('indigoObject'));
var ffi = require('ffi');

var Indigo = function (options) {
	options = options || {};
	var libpath = local('shared/' + process.platform + '/' + process.arch + '/'+ config[process.platform].libs['indigo']);
	this.libpath = options.libpath || libpath;
	this.logger = options.logger || console;
	this._lib = ffi.Library(libpath, lib_api.api);
	// Allocate a new session. Each session has its own
	// set of objects created and options set up.
	this._sid = this._lib.indigoAllocSessionId();
	this._out = lib_api.out;
	this._type = lib_api.type;
};

/*
 * Return Indigo version string.
 *
 * @method getVersion
 * @return {String} Indigo version
 */
Indigo.prototype.getVersion = function () {
	return "Indigo version(" + this._lib.indigoVersion() + ");";
};

/* System */

/*
 * Switch to another session. The session, if was not allocated
 * previously, is allocated automatically and initialized with
 * empty set of objects and default options.
 *
 * @method _setSessionId
 */
Indigo.prototype._setSessionId = function () {
	this._lib.indigoSetSessionId(this._sid)
};

/*
 * Release session. The memory used by the released session
 * is not freed, but the number will be reused on
 * further allocations.
 *
 * @method _releaseSessionId
 */
Indigo.prototype._releaseSessionId = function () {
	if (this._lib)
		this._lib.indigoReleaseSessionId(this._sid);
};

/*
 * Get the last error message
 *
 * @method getLastError
 */
Indigo.prototype.getLastError = function () {
	if (this._lib)
		return this._lib.indigoGetLastError();
	return '';
};

/*
 * Check result
 *
 * @method _checkResult
 * @param {number} result
 */
Indigo.prototype._checkResult = function (result) {
	if (result < 0) {
		var msg = this.getLastError();
		this.logger.error('res < 0[' + result + ']: ' + msg);
	}
	return result;
};

/*
 * Check string result
 *
 * @method _checkResultString
 * @param {string} result
 */
Indigo.prototype._checkResultString = function (result) {
	if (typeof result !== 'string')
		this.logger.error("the result isn't string");
	return result;
};

/*
 * Count object currently allocated
 *
 * @method countReferences
 * @return {number} count
 */
Indigo.prototype.countReferences = function () {
	this._setSessionId();
	return this._checkResult(this._lib.indigoCountReferences());
};

/*
 * Load molecule from string
 *
 * @method loadMolecule
 * @param {string} string reprsantation of molecule or a specification in form of a line notation for describing the structure of chemical species using short ASCII strings.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadMolecule = function (string) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadMoleculeFromString(string)));
};

/*
 * Load query molecule from string
 *
 * @method loadQueryMolecule
 * @param {string} string reprsantation of query molecule or a specification in form of a line notation for describing the structure of chemical species using short ASCII strings.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadQueryMolecule = function (string) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadQueryMoleculeFromString(string)));
};

/*
 * Load query molecule from file
 *
 * @method loadQueryMoleculeFromFile
 * @param {string} filename of chemical format of molecule.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadQueryMoleculeFromFile = function (filename) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadQueryMoleculeFromFile(filename)));
};

/*
 * Load molecule from file
 *
 * @method loadMoleculeFromFile
 * @param {string} filename of chemical format of molecule.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadMoleculeFromFile = function (filename) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadMoleculeFromFile(filename)));
};

/*
 *  Substructure matching
 *
 * @method substructureMatcher
 * @param {object} target is IndigoObject
 * @param {string} 'mode' is reserved for future use; currently its value is ignored
 * @return {object} a new 'matcher' object
 */
Indigo.prototype.substructureMatcher = function (target, mode) {
	this._setSessionId();
	if (mode === undefined || mode === null) {
		mode = '';
	}
	return new IndigoObject(this, this._checkResult(this._lib.indigoSubstructureMatcher(target.id, mode)), target);
};

/*
 *
 *
 * @method unserialize
 * @param {array}
 * @return {object} a new indigo object
 */
Indigo.prototype.unserialize = function (array) {
	this._setSessionId();
	var buf = new Buffer(array);
	var pointer = this._out.alloc(this._type.byte_ptr, buf);
	var res = this._lib.indigoUnserialize(pointer.deref(), buf.length);
	return new IndigoObject(this, this._checkResult(res));
};

/*
 * Set Option
 *
 * @method setOption
 * @param {string} name of option.
 * @param {number or string or boolean} value of option.
 * @return {boolean} return true if option applies as successful
 */
Indigo.prototype.setOption = function (option, value1, value2, value3) {
	this._setSessionId();
	var ret = -1;
	var value2 = value2 || 0;
	var value3 = value3 || 0;
	if (!value2) {
		switch (typeof value1) {
			case 'string':
				ret = this._checkResult(this._lib.indigoSetOption(option, value1));
				break;
			case 'number':
				{
					if (/^[0-9]+$/.test(String(value1)))
						ret = this._checkResult(this._lib.indigoSetOptionInt(option, value1));
					else
						ret = this._checkResult(this._lib.indigoSetOptionFloat(option, value1));
				}
				break;
			case 'boolean':
				var value1_b = (value1)?1:0;
				ret = this._checkResult(this._lib.indigoSetOptionBool(option, value1_b));
				break;
			default:
				this.logger.error("bad option");
		}
	}
	else {
		if (typeof value1 === 'number' && typeof value2 === 'number') {
			if ((/^[0-9]+$/.test(String(value1))) && (/^[0-9]+$/.test(String(value2))))
				ret = this._checkResult(this._lib.indigoSetOptionXY(option, value1, value2));
			else
				this.logger.error("bad option");
		}
		if (typeof value1 === 'number' && typeof value2 === 'number' && typeof value3 === 'number') {
			if (!(/^[0-9]+$/.test(String(value1))) && !(/^[0-9]+$/.test(String(value2))) && !(/^[0-9]+$/.test(String(value3))))
				ret = this._checkResult(this._lib.indigoSetOptionColor(option, value1, value2, value3));
			else
				this.logger.error("bad option");
		}
	}
	return (ret === 1);
};

module.exports = new Indigo();
<commit_msg>add Indigo.loadSmarts  * Load molecular pattern from SMARTS string format  *  * @method loadSmarts  * @param {string} a particular pattern (subgraph) in a molecule (graph)  * @return {object} IndigoObject and Indigo.loadSmartsFromFile  * Load molecular pattern from file which contains SMARTS string format  *  * @method loadSmartsFromFile  * @param {string} a particular pattern (subgraph) in a molecule (graph)  * @return {object} IndigoObject<commit_after>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/
var path = require('path');
var local = path.join.bind(path, __dirname);
var config = require(local('configureIndigo'));
var lib_api = require(local('indigo-api'));
var IndigoObject = require(local('indigoObject'));
var ffi = require('ffi');

var Indigo = function (options) {
	options = options || {};
	var libpath = local('shared/' + process.platform + '/' + process.arch + '/'+ config[process.platform].libs['indigo']);
	this.libpath = options.libpath || libpath;
	this.logger = options.logger || console;
	this._lib = ffi.Library(libpath, lib_api.api);
	// Allocate a new session. Each session has its own
	// set of objects created and options set up.
	this._sid = this._lib.indigoAllocSessionId();
	this._out = lib_api.out;
	this._type = lib_api.type;
};

/*
 * Return Indigo version string.
 *
 * @method getVersion
 * @return {String} Indigo version
 */
Indigo.prototype.getVersion = function () {
	return "Indigo version(" + this._lib.indigoVersion() + ");";
};

/* System */

/*
 * Switch to another session. The session, if was not allocated
 * previously, is allocated automatically and initialized with
 * empty set of objects and default options.
 *
 * @method _setSessionId
 */
Indigo.prototype._setSessionId = function () {
	this._lib.indigoSetSessionId(this._sid)
};

/*
 * Release session. The memory used by the released session
 * is not freed, but the number will be reused on
 * further allocations.
 *
 * @method _releaseSessionId
 */
Indigo.prototype._releaseSessionId = function () {
	if (this._lib)
		this._lib.indigoReleaseSessionId(this._sid);
};

/*
 * Get the last error message
 *
 * @method getLastError
 */
Indigo.prototype.getLastError = function () {
	if (this._lib)
		return this._lib.indigoGetLastError();
	return '';
};

/*
 * Check result
 *
 * @method _checkResult
 * @param {number} result
 */
Indigo.prototype._checkResult = function (result) {
	if (result < 0) {
		var msg = this.getLastError();
		this.logger.error('res < 0[' + result + ']: ' + msg);
	}
	return result;
};

/*
 * Check string result
 *
 * @method _checkResultString
 * @param {string} result
 */
Indigo.prototype._checkResultString = function (result) {
	if (typeof result !== 'string')
		this.logger.error("the result isn't string");
	return result;
};

/*
 * Count object currently allocated
 *
 * @method countReferences
 * @return {number} count
 */
Indigo.prototype.countReferences = function () {
	this._setSessionId();
	return this._checkResult(this._lib.indigoCountReferences());
};

/*
 * Load molecule from string
 *
 * @method loadMolecule
 * @param {string} string reprsantation of molecule or a specification in form of a line notation for describing the structure of chemical species using short ASCII strings.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadMolecule = function (string) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadMoleculeFromString(string)));
};

/*
 * Load query molecule from string
 *
 * @method loadQueryMolecule
 * @param {string} string reprsantation of query molecule or a specification in form of a line notation for describing the structure of chemical species using short ASCII strings.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadQueryMolecule = function (string) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadQueryMoleculeFromString(string)));
};

/*
 * Load query molecule from file
 *
 * @method loadQueryMoleculeFromFile
 * @param {string} filename of chemical format of molecule.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadQueryMoleculeFromFile = function (filename) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadQueryMoleculeFromFile(filename)));
};

/*
 * Load molecule from file
 *
 * @method loadMoleculeFromFile
 * @param {string} filename of chemical format of molecule.
 * @return {object} IndigoObject
 */
Indigo.prototype.loadMoleculeFromFile = function (filename) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadMoleculeFromFile(filename)));
};

/*
 * Load molecular pattern from SMARTS string format
 *
 * @method loadSmarts
 * @param {string} a particular pattern (subgraph) in a molecule (graph)
 * @return {object} IndigoObject
 */
Indigo.prototype.loadSmarts = function (string) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadSmartsFromString(string)));
}

/*
 * Load molecular pattern from file which contains SMARTS string format
 *
 * @method loadSmartsFromFile
 * @param {string} a particular pattern (subgraph) in a molecule (graph)
 * @return {object} IndigoObject
 */
Indigo.prototype.loadSmartsFromFile = function (filename) {
	this._setSessionId();
	return new IndigoObject(this, this._checkResult(this._lib.indigoLoadSmartsFromFile(filename)));
}

/*
 *  Substructure matching
 *
 * @method substructureMatcher
 * @param {object} target is IndigoObject
 * @param {string} 'mode' is reserved for future use; currently its value is ignored
 * @return {object} a new 'matcher' object
 */
Indigo.prototype.substructureMatcher = function (target, mode) {
	this._setSessionId();
	if (mode === undefined || mode === null) {
		mode = '';
	}
	return new IndigoObject(this, this._checkResult(this._lib.indigoSubstructureMatcher(target.id, mode)), target);
};

/*
 *
 *
 * @method unserialize
 * @param {array}
 * @return {object} a new indigo object
 */
Indigo.prototype.unserialize = function (array) {
	this._setSessionId();
	var buf = new Buffer(array);
	var pointer = this._out.alloc(this._type.byte_ptr, buf);
	var res = this._lib.indigoUnserialize(pointer.deref(), buf.length);
	return new IndigoObject(this, this._checkResult(res));
};

/*
 * Set Option
 *
 * @method setOption
 * @param {string} name of option.
 * @param {number or string or boolean} value of option.
 * @return {boolean} return true if option applies as successful
 */
Indigo.prototype.setOption = function (option, value1, value2, value3) {
	this._setSessionId();
	var ret = -1;
	var value2 = value2 || 0;
	var value3 = value3 || 0;
	if (!value2) {
		switch (typeof value1) {
			case 'string':
				ret = this._checkResult(this._lib.indigoSetOption(option, value1));
				break;
			case 'number':
				{
					if (/^[0-9]+$/.test(String(value1)))
						ret = this._checkResult(this._lib.indigoSetOptionInt(option, value1));
					else
						ret = this._checkResult(this._lib.indigoSetOptionFloat(option, value1));
				}
				break;
			case 'boolean':
				var value1_b = (value1)?1:0;
				ret = this._checkResult(this._lib.indigoSetOptionBool(option, value1_b));
				break;
			default:
				this.logger.error("bad option");
		}
	}
	else {
		if (typeof value1 === 'number' && typeof value2 === 'number') {
			if ((/^[0-9]+$/.test(String(value1))) && (/^[0-9]+$/.test(String(value2))))
				ret = this._checkResult(this._lib.indigoSetOptionXY(option, value1, value2));
			else
				this.logger.error("bad option");
		}
		if (typeof value1 === 'number' && typeof value2 === 'number' && typeof value3 === 'number') {
			if (!(/^[0-9]+$/.test(String(value1))) && !(/^[0-9]+$/.test(String(value2))) && !(/^[0-9]+$/.test(String(value3))))
				ret = this._checkResult(this._lib.indigoSetOptionColor(option, value1, value2, value3));
			else
				this.logger.error("bad option");
		}
	}
	return (ret === 1);
};

module.exports = new Indigo();
<|endoftext|>


<commit_before>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/
var path = require("path");
var local = path.join.bind(path, __dirname);
var config = require(local("configureIndigo"));
var libs_api = require(local("indigo-api"));
var ffi = require('ffi');

var Indigo = function (options) {
	options = options || {};
	var libpath = local('shared/' + process.platform + '/' + process.arch + '/indigo');
	this.libpath = options.libpath || libpath;
	this._lib = ffi.Library(libpath, libs_api);
	// Allocate a new session. Each session has its own
	// set of objects created and options set up.
	this._sid = this._lib.indigoAllocSessionId();
};

/*
 * Return Indigo version string.
 *
 * @method getVersion
 * @return {String} Indigo version
 */
Indigo.prototype.getVersion = function () {
	return "Indigo version(" + this._lib.indigoVersion() + ");";
};

/* System */

/*
 * Switch to another session. The session, if was not allocated
 * previously, is allocated automatically and initialized with
 * empty set of objects and default options.
 *
 * @method _setSessionId
 */
Indigo.prototype._setSessionId = function () {
	this._lib.indigoSetSessionId(this._sid)
};

/*
 * Release session. The memory used by the released session
 * is not freed, but the number will be reused on
 * further allocations.
 *
 * @method _releaseSessionId
 */
Indigo.prototype._releaseSessionId = function () {
	if (this._lib)
		this._lib.indigoReleaseSessionId(this._sid);
};

module.exports = new Indigo();
<commit_msg>add getLastError  * Get the last error message<commit_after>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/
var path = require("path");
var local = path.join.bind(path, __dirname);
var config = require(local("configureIndigo"));
var libs_api = require(local("indigo-api"));
var ffi = require('ffi');

var Indigo = function (options) {
	options = options || {};
	var libpath = local('shared/' + process.platform + '/' + process.arch + '/indigo');
	this.libpath = options.libpath || libpath;
	this._lib = ffi.Library(libpath, libs_api);
	// Allocate a new session. Each session has its own
	// set of objects created and options set up.
	this._sid = this._lib.indigoAllocSessionId();
};

/*
 * Return Indigo version string.
 *
 * @method getVersion
 * @return {String} Indigo version
 */
Indigo.prototype.getVersion = function () {
	return "Indigo version(" + this._lib.indigoVersion() + ");";
};

/* System */

/*
 * Switch to another session. The session, if was not allocated
 * previously, is allocated automatically and initialized with
 * empty set of objects and default options.
 *
 * @method _setSessionId
 */
Indigo.prototype._setSessionId = function () {
	this._lib.indigoSetSessionId(this._sid)
};

/*
 * Release session. The memory used by the released session
 * is not freed, but the number will be reused on
 * further allocations.
 *
 * @method _releaseSessionId
 */
Indigo.prototype._releaseSessionId = function () {
	if (this._lib)
		this._lib.indigoReleaseSessionId(this._sid);
};

/*
 * Get the last error message
 *
 * @method getLastError
 */
Indigo.prototype.getLastError = function () {
	if (this._lib)
		return this._lib.indigoGetLastError();
	return '';
};

module.exports = new Indigo();
<|endoftext|>


<commit_before>import React, {useState} from 'react';
import {Platform} from 'react-native';
import {StateNavigator} from 'navigation';
import {NavigationHandler} from 'navigation-react';
import {NavigationStack, TabBar, TabBarItem} from 'navigation-react-native';
import Home from './Home';
import Notifications from './Notifications';
import Tabs from './Tabs';
import Tweet from './Tweet';
import Timeline from './Timeline';
import {getHome, getFollows, getTweet, getTimeline} from './data';

const stateNavigator = new StateNavigator([
  {key: 'home'},
  {key: 'notifications'},
  {key: 'tweet', trackCrumbTrail: true},
  {key: 'timeline', trackCrumbTrail: true}
]);
const {home, notifications, tweet, timeline} = stateNavigator.states;
const HomeLayout = Platform.OS === 'ios' ? Home : Tabs;
home.renderScene = () => <HomeLayout tweets={getHome()} follows={getFollows()} />;
notifications.renderScene = () => <Notifications follows={getFollows()} />;
tweet.renderScene = ({id}) => <Tweet tweet={getTweet(id)}  />;
timeline.renderScene = ({id}) => <Timeline timeline={getTimeline(id)}  />;

const notificationsNavigator = new StateNavigator(stateNavigator);
stateNavigator.navigate('home');
notificationsNavigator.navigate('notifications');

export default () => {
  const [notified, setNotified] = useState(false);
  return Platform.OS === 'ios' ? (
    <TabBar bottomTabs={true}>
      <TabBarItem title="Home">
        <NavigationHandler stateNavigator={stateNavigator}>
          <NavigationStack
            crumbStyle={from => from ? 'scale_in' : 'scale_out'}
            unmountStyle={from => from ? 'slide_in' : 'slide_out'} />
        </NavigationHandler>
      </TabBarItem>
      <TabBarItem
        title="Notifications"
        badge={!notified ? getFollows().length : null}
        onPress={() => {setNotified(true)}}>
        <NavigationHandler stateNavigator={notificationsNavigator}>
          <NavigationStack
            primary={false}
            crumbStyle={from => from ? 'scale_in' : 'scale_out'}
            unmountStyle={from => from ? 'slide_in' : 'slide_out'} />
        </NavigationHandler>
      </TabBarItem>
    </TabBar>
  ) : (
    <NavigationHandler stateNavigator={stateNavigator}>
      <NavigationStack
        crumbStyle={from => from ? 'scale_in' : 'scale_out'}
        unmountStyle={from => from ? 'slide_in' : 'slide_out'} />
    </NavigationHandler>
  );
}
<commit_msg>Extracted stack into separate component<commit_after>import React, {useState} from 'react';
import {Platform} from 'react-native';
import {StateNavigator} from 'navigation';
import {NavigationHandler} from 'navigation-react';
import {NavigationStack, TabBar, TabBarItem} from 'navigation-react-native';
import Home from './Home';
import Notifications from './Notifications';
import Tabs from './Tabs';
import Tweet from './Tweet';
import Timeline from './Timeline';
import {getHome, getFollows, getTweet, getTimeline} from './data';

const stateNavigator = new StateNavigator([
  {key: 'home'},
  {key: 'notifications'},
  {key: 'tweet', trackCrumbTrail: true},
  {key: 'timeline', trackCrumbTrail: true}
]);
const {home, notifications, tweet, timeline} = stateNavigator.states;
const HomeLayout = Platform.OS === 'ios' ? Home : Tabs;
home.renderScene = () => <HomeLayout tweets={getHome()} follows={getFollows()} />;
notifications.renderScene = () => <Notifications follows={getFollows()} />;
tweet.renderScene = ({id}) => <Tweet tweet={getTweet(id)}  />;
timeline.renderScene = ({id}) => <Timeline timeline={getTimeline(id)}  />;

const notificationsNavigator = new StateNavigator(stateNavigator);
stateNavigator.navigate('home');
notificationsNavigator.navigate('notifications');

const Stack = ({navigator}) => (
  <NavigationHandler stateNavigator={navigator}>
    <NavigationStack
      crumbStyle={from => from ? 'scale_in' : 'scale_out'}
      unmountStyle={from => from ? 'slide_in' : 'slide_out'} />
  </NavigationHandler>
);

const App = () => {
  const [notified, setNotified] = useState(false);
  return Platform.OS === 'ios' ? (
    <TabBar bottomTabs={true}>
      <TabBarItem title="Home">
        <Stack navigator={stateNavigator} />
      </TabBarItem>
      <TabBarItem
        title="Notifications"
        badge={!notified ? getFollows().length : null}
        onPress={() => {setNotified(true)}}>
        <Stack navigator={notificationsNavigator} />
      </TabBarItem>
    </TabBar>
  ) : (
    <Stack navigator={stateNavigator} />
  );
};

export default App;
<|endoftext|>


<commit_before>/*global availity, it, inject, jasmine, module, spyOn, beforeEach, expect, describe */
describe('avMessagesProvider', function() {

  'use strict';

  var avMessagesProvider;
  var $rootScope;
  var avMessages;
  var AV_MESSAGES;

  availity.mock.providerSpecHelper();

  beforeEach(module('availity', function(_avMessagesProvider_) {

    avMessagesProvider = _avMessagesProvider_;
    avMessagesProvider.enable(false);

  }));

  beforeEach(inject(function(_$rootScope_, _avMessages_, _AV_MESSAGES_) {
    $rootScope = _$rootScope_;
    avMessages = _avMessages_;
    AV_MESSAGES = _AV_MESSAGES_;
  }));

  it('should be defined', function() {
    expect(avMessagesProvider.$get).toBeDefined();
    expect(avMessagesProvider.enable).toBeDefined();
  });

  describe('avMessages', function() {

    function testDomain(domain) {
      avMessages.domain = jasmine.createSpy().and.returnValue(domain);
      expect(avMessages.isDomain(domain)).toBeTruthy();
    }

    it('regex should validate availity.com domains', function() {

      testDomain('https://availity.com');
      testDomain('https://availity.com');
      testDomain('https://www.availity.com');
      testDomain('https://apps.availity.com');
      testDomain('https://test-apps.availity.com');
      testDomain('https://availity.net');

      testDomain('http://localhost:3000');
      testDomain('http://0.0.0.0:3000');
      testDomain('http://127.0.0.1:3000');

    });

    it('should send AV_MAXIMIZE event on init()', function() {

      spyOn(avMessages, 'send');
      avMessages.init();

      expect(avMessages.send).toHaveBeenCalledWith(AV_MESSAGES.EVENTS.AV_MAXIMIZE);

      avMessages.destroy();

    });

    beforeEach(function() {

      // Make debounce sync
      spyOn(_, 'debounce').and.callFake(function(func) {
        return function () {
          func.apply(this, arguments);
        };
      });

    });

    it('should send AV_RESIZE when window is resized', function() {

      avMessages.init();

      spyOn(avMessages, 'onResize').and.callThrough();
      spyOn(avMessages, 'send');

      window.scroll(0, 1000);
      $(window).trigger("resize");

      expect(avMessages.onResize).toHaveBeenCalled();
      expect(avMessages.send).toHaveBeenCalled();

      var payload = avMessages.send.calls.mostRecent().args[0];
      expect(payload.event).toBe(AV_MESSAGES.EVENTS.AV_RESIZE);
      expect(payload.height).toBeDefined();

      avMessages.destroy();

    });

    it('send() should post string event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');
      avMessages.send('hipaa');
      expect(window.parent.postMessage).toHaveBeenCalledWith('hipaa', jasmine.any(String));

    });

    it('send() should post object event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');

      avMessages.send({event: 'resize', height: '45px'});

      expect(window.parent.postMessage).toHaveBeenCalledWith('{"event":"resize","height":"45px"}', jasmine.any(String));

    });

    it('onMessage() should emit string event', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: 'window:resize'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', null);

    });

    it('onMessage() should emit string event', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: '{"event":"window:resize","height":"45px"}'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', {"event":"window:resize","height":"45px"});

    });

  });

});
<commit_msg>update spec description<commit_after>/*global availity, it, inject, jasmine, module, spyOn, beforeEach, expect, describe */
describe('avMessagesProvider', function() {

  'use strict';

  var avMessagesProvider;
  var $rootScope;
  var avMessages;
  var AV_MESSAGES;

  availity.mock.providerSpecHelper();

  beforeEach(module('availity', function(_avMessagesProvider_) {

    avMessagesProvider = _avMessagesProvider_;
    avMessagesProvider.enable(false);

  }));

  beforeEach(inject(function(_$rootScope_, _avMessages_, _AV_MESSAGES_) {
    $rootScope = _$rootScope_;
    avMessages = _avMessages_;
    AV_MESSAGES = _AV_MESSAGES_;
  }));

  it('should be defined', function() {
    expect(avMessagesProvider.$get).toBeDefined();
    expect(avMessagesProvider.enable).toBeDefined();
  });

  describe('avMessages', function() {

    function testDomain(domain) {
      avMessages.domain = jasmine.createSpy().and.returnValue(domain);
      expect(avMessages.isDomain(domain)).toBeTruthy();
    }

    it('regex should validate availity.com domains', function() {

      testDomain('https://availity.com');
      testDomain('https://availity.com');
      testDomain('https://www.availity.com');
      testDomain('https://apps.availity.com');
      testDomain('https://test-apps.availity.com');
      testDomain('https://availity.net');

      testDomain('http://localhost:3000');
      testDomain('http://0.0.0.0:3000');
      testDomain('http://127.0.0.1:3000');

    });

    it('should send AV_MAXIMIZE event on init()', function() {

      spyOn(avMessages, 'send');
      avMessages.init();

      expect(avMessages.send).toHaveBeenCalledWith(AV_MESSAGES.EVENTS.AV_MAXIMIZE);

      avMessages.destroy();

    });

    beforeEach(function() {

      // Make debounce sync
      spyOn(_, 'debounce').and.callFake(function(func) {
        return function () {
          func.apply(this, arguments);
        };
      });

    });

    it('should send AV_RESIZE when window is resized', function() {

      avMessages.init();

      spyOn(avMessages, 'onResize').and.callThrough();
      spyOn(avMessages, 'send');

      window.scroll(0, 1000);
      $(window).trigger("resize");

      expect(avMessages.onResize).toHaveBeenCalled();
      expect(avMessages.send).toHaveBeenCalled();

      var payload = avMessages.send.calls.mostRecent().args[0];
      expect(payload.event).toBe(AV_MESSAGES.EVENTS.AV_RESIZE);
      expect(payload.height).toBeDefined();

      avMessages.destroy();

    });

    it('send() should post string event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');
      avMessages.send('hipaa');
      expect(window.parent.postMessage).toHaveBeenCalledWith('hipaa', jasmine.any(String));

    });

    it('send() should post object event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');

      avMessages.send({event: 'resize', height: '45px'});

      expect(window.parent.postMessage).toHaveBeenCalledWith('{"event":"resize","height":"45px"}', jasmine.any(String));

    });

    it('onMessage() should emit string event', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: 'window:resize'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', null);

    });

    it('onMessage() should emit string event and object', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: '{"event":"window:resize","height":"45px"}'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', {"event":"window:resize","height":"45px"});

    });

  });

});
<|endoftext|>


<commit_before>import React, { Component } from "react";
import { get, set, isString, isFunction, reduce } from "lodash";
import shallowEqual from "shallowequal";

import checkStatus from "./check-status";
import parseJSON from "./parse-json";

const connect = fn => DecoratedComponent =>
	class FetchOnUpdateDecorator extends Component {
		state = {};

		componentDidMount() {
			if (this.props.disableFetch) return;

			const itemsToFetch = fn(this.props);

			if (itemsToFetch) {
				for (let key in itemsToFetch) {
					const item = itemsToFetch[key];
					this.processItem(key, item);
				}
			}
		}

		componentDidUpdate(prevProps) {
			if (this.props.disableFetch) return;

			const itemsToFetch = fn(this.props);

			if (itemsToFetch) {
				for (let key in itemsToFetch) {
					const item = itemsToFetch[key];
					const keys = get(item, "keys");

					// if they didn't specify any keys, we effectively only run the fetch function once on init
					if (keys && keys.length) {
						const params = mapParams(keys, this.props);
						const prevParams = mapParams(keys, prevProps);

						if (!shallowEqual(params, prevParams)) {
							this.processItem(key, item);
						}
					}
				}
			}
		}

		componentWillUnMount() {
			for (let key in this.timers) {
				clearTimeout(this.timers[key]);
			}

			this.timers = {};
		}

		timers = {};

		processItem(key, item) {
			if (!item) return;

			if (isFunction(item)) {
				this.createLazyFunc(key, item);
			} else {
				this.doFetch(key, item);
			}
		}

		createLazyFunc(key, lazyFn) {
			this.setState({
				[key]: (...args) => {
					const result = lazyFn(...args);
					if (result) {
						for (let subkey in result) {
							const item = result[subkey];
							this.processItem(subkey, item);
						}
					}
				}
			});
		}

		async doFetch(key, item) {
			let { url, onData, onComplete, bearerToken, reset, ...opts } = item;

			if (!url && isString(item)) {
				url = item;
			}

			let { headers, ...otherOpts } = opts;
			headers = headers || {};

			if (!headers["Accept"]) {
				headers["Accept"] = "application/json";
			}

			if (!headers["Content-Type"]) {
				headers["Content-Type"] = "application/json";
			}

			if (bearerToken) {
				headers["Authorization"] = `Bearer ${bearerToken}`;
			}

			if (this.timers[key]) {
				// clear any pending reset
				clearTimeout(this.timers[key]);
				delete this.timers[key];
			}

			this.setState(prevState => ({
				[key]: {
					data: get(prevState, `${key}.data`),
					isFetching: true,
					isFetched: !!get(prevState, `${key}.isFetched`), // if this is first time we are fetching, need to set isFetched to a bool
					error: null
				}
			}));

			try {
				let response = await fetch(url, { ...otherOpts, headers });

				response = await checkStatus(response);

				if (response.status != 204) {
					response = await parseJSON(response);

					if (onData) {
						response = onData(response);
						if (response.then) {
							response = await handleAsyncOnDatas(response);
						}
					}
				} else {
					// for 204 No Content, just return null data
					response = null;
				}

				this.setState(
					{
						[key]: {
							data: response,
							isFetching: false,
							isFetched: true,
							error: null
						}
					},
					() => {
						if (onComplete) {
							const data = reduce(
								Object.keys(this.state),
								(result, k) => ({
									...result,
									[k]: get(this.state, `${k}.data`)
								}),
								{}
							);

							const manipulatedData = onComplete(data);

							if (manipulatedData) {
								this.setState(prevState =>
									reduce(
										Object.keys(manipulatedData),
										(result, k) => ({
											...result,
											[k]: {
												...get(prevState, k, {}),
												data: manipulatedData[k]
											}
										}),
										{}
									)
								);
							}
						}
					}
				);

				if (reset) {
					this.timers[key] = setTimeout(() => {
						// reset the state
						this.setState({
							[key]: {
								data: null,
								isFetching: false,
								isFetched: false,
								error: null
							}
						});

						delete this.timers[key];
					}, reset);
				}
			} catch (ex) {
				this.setState(prevState => ({
					[key]: {
						data: prevState[key].data,
						isFetching: false,
						isFetched: prevState[key].isFetched,
						error: ex.message
					}
				}));
			}
		}

		render() {
			return <DecoratedComponent {...this.props} {...this.state} />;
		}
	};

export function mapParams(paramKeys, params) {
	const result = {};

	paramKeys.forEach(path => {
		const value = get(params, path);

		// move any nested paths to the root of the result
		// for the purpose of doing a shallow comparison
		path = path.replace(/\./g, "_");

		set(result, path, value);
	});

	return result;
}

const handleAsyncOnDatas = async response => {
	try {
		response = await response;
		if (response.then) {
			response = await handleAsyncOnDatas(response);
		}
		return response;
	} catch (ex) {
		throw ex;
	}
};

export default connect;
<commit_msg>Removes unnecessary try/catch<commit_after>import React, { Component } from "react";
import { get, set, isString, isFunction, reduce } from "lodash";
import shallowEqual from "shallowequal";

import checkStatus from "./check-status";
import parseJSON from "./parse-json";

const connect = fn => DecoratedComponent =>
	class FetchOnUpdateDecorator extends Component {
		state = {};

		componentDidMount() {
			if (this.props.disableFetch) return;

			const itemsToFetch = fn(this.props);

			if (itemsToFetch) {
				for (let key in itemsToFetch) {
					const item = itemsToFetch[key];
					this.processItem(key, item);
				}
			}
		}

		componentDidUpdate(prevProps) {
			if (this.props.disableFetch) return;

			const itemsToFetch = fn(this.props);

			if (itemsToFetch) {
				for (let key in itemsToFetch) {
					const item = itemsToFetch[key];
					const keys = get(item, "keys");

					// if they didn't specify any keys, we effectively only run the fetch function once on init
					if (keys && keys.length) {
						const params = mapParams(keys, this.props);
						const prevParams = mapParams(keys, prevProps);

						if (!shallowEqual(params, prevParams)) {
							this.processItem(key, item);
						}
					}
				}
			}
		}

		componentWillUnMount() {
			for (let key in this.timers) {
				clearTimeout(this.timers[key]);
			}

			this.timers = {};
		}

		timers = {};

		processItem(key, item) {
			if (!item) return;

			if (isFunction(item)) {
				this.createLazyFunc(key, item);
			} else {
				this.doFetch(key, item);
			}
		}

		createLazyFunc(key, lazyFn) {
			this.setState({
				[key]: (...args) => {
					const result = lazyFn(...args);
					if (result) {
						for (let subkey in result) {
							const item = result[subkey];
							this.processItem(subkey, item);
						}
					}
				}
			});
		}

		async doFetch(key, item) {
			let { url, onData, onComplete, bearerToken, reset, ...opts } = item;

			if (!url && isString(item)) {
				url = item;
			}

			let { headers, ...otherOpts } = opts;
			headers = headers || {};

			if (!headers["Accept"]) {
				headers["Accept"] = "application/json";
			}

			if (!headers["Content-Type"]) {
				headers["Content-Type"] = "application/json";
			}

			if (bearerToken) {
				headers["Authorization"] = `Bearer ${bearerToken}`;
			}

			if (this.timers[key]) {
				// clear any pending reset
				clearTimeout(this.timers[key]);
				delete this.timers[key];
			}

			this.setState(prevState => ({
				[key]: {
					data: get(prevState, `${key}.data`),
					isFetching: true,
					isFetched: !!get(prevState, `${key}.isFetched`), // if this is first time we are fetching, need to set isFetched to a bool
					error: null
				}
			}));

			try {
				let response = await fetch(url, { ...otherOpts, headers });

				response = await checkStatus(response);

				if (response.status != 204) {
					response = await parseJSON(response);

					if (onData) {
						response = onData(response);
						if (response.then) {
							response = await handleAsyncOnDatas(response);
						}
					}
				} else {
					// for 204 No Content, just return null data
					response = null;
				}

				this.setState(
					{
						[key]: {
							data: response,
							isFetching: false,
							isFetched: true,
							error: null
						}
					},
					() => {
						if (onComplete) {
							const data = reduce(
								Object.keys(this.state),
								(result, k) => ({
									...result,
									[k]: get(this.state, `${k}.data`)
								}),
								{}
							);

							const manipulatedData = onComplete(data);

							if (manipulatedData) {
								this.setState(prevState =>
									reduce(
										Object.keys(manipulatedData),
										(result, k) => ({
											...result,
											[k]: {
												...get(prevState, k, {}),
												data: manipulatedData[k]
											}
										}),
										{}
									)
								);
							}
						}
					}
				);

				if (reset) {
					this.timers[key] = setTimeout(() => {
						// reset the state
						this.setState({
							[key]: {
								data: null,
								isFetching: false,
								isFetched: false,
								error: null
							}
						});

						delete this.timers[key];
					}, reset);
				}
			} catch (ex) {
				this.setState(prevState => ({
					[key]: {
						data: prevState[key].data,
						isFetching: false,
						isFetched: prevState[key].isFetched,
						error: ex.message
					}
				}));
			}
		}

		render() {
			return <DecoratedComponent {...this.props} {...this.state} />;
		}
	};

export function mapParams(paramKeys, params) {
	const result = {};

	paramKeys.forEach(path => {
		const value = get(params, path);

		// move any nested paths to the root of the result
		// for the purpose of doing a shallow comparison
		path = path.replace(/\./g, "_");

		set(result, path, value);
	});

	return result;
}

const handleAsyncOnDatas = async response => {
	response = await response;
	if (response.then) {
		response = await handleAsyncOnDatas(response);
	}
	return response;
};

export default connect;
<|endoftext|>


<commit_before>/*global availity, it, inject, jasmine, module, spyOn, beforeEach, expect, describe */
describe('avMessagesProvider', function() {

  'use strict';

  var avMessagesProvider;
  var $rootScope;
  var avMessages;
  var AV_MESSAGES;

  availity.mock.providerSpecHelper();

  beforeEach(module('availity', function(_avMessagesProvider_) {

    avMessagesProvider = _avMessagesProvider_;
    avMessagesProvider.enable(false);

  }));

  beforeEach(inject(function(_$rootScope_, _avMessages_, _AV_MESSAGES_) {
    $rootScope = _$rootScope_;
    avMessages = _avMessages_;
    AV_MESSAGES = _AV_MESSAGES_;
  }));

  it('should be defined', function() {
    expect(avMessagesProvider.$get).toBeDefined();
    expect(avMessagesProvider.enable).toBeDefined();
  });

  describe('avMessages', function() {

    function testDomain(domain) {
      avMessages.domain = jasmine.createSpy().and.returnValue(domain);
      expect(avMessages.isDomain(domain)).toBeTruthy();
    }

    it('regex should validate availity.com domains', function() {

      testDomain('https://availity.com');
      testDomain('https://availity.com');
      testDomain('https://www.availity.com');
      testDomain('https://apps.availity.com');
      testDomain('https://test-apps.availity.com');
      testDomain('https://availity.net');

      testDomain('http://localhost:3000');
      testDomain('http://0.0.0.0:3000');
      testDomain('http://127.0.0.1:3000');

    });

    it('should send AV_MAXIMIZE event on init()', function() {

      spyOn(avMessages, 'send');
      avMessages.init();

      expect(avMessages.send).toHaveBeenCalledWith(AV_MESSAGES.EVENTS.AV_MAXIMIZE);

      avMessages.destroy();

    });

    beforeEach(function() {

      // Make debounce sync
      spyOn(_, 'debounce').and.callFake(function(func) {
        return function () {
          func.apply(this, arguments);
        };
      });

    });

    it('should send AV_RESIZE when window is resized', function() {

      avMessages.init();

      spyOn(avMessages, 'onResize').and.callThrough();
      spyOn(avMessages, 'send');

      window.scroll(0, 1000);
      $(window).trigger("resize");

      expect(avMessages.onResize).toHaveBeenCalled();
      expect(avMessages.send).toHaveBeenCalled();

      var payload = avMessages.send.calls.mostRecent().args[0];
      expect(payload.event).toBe(AV_MESSAGES.EVENTS.AV_RESIZE);
      expect(payload.height).toBeDefined();

      avMessages.destroy();

    });

    // it('send() should post string event', function(done) {

    //   var win = window.parent;

    //   $(window.parent).on('message', function(e) {
    //     debugger;
    //     expect(e.originalEvent.data).toBe('hipaa');
    //     done();
    //   });

    //   avMessages.send('hipaa');

    // });
    //

    it('send() should post string event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');
      avMessages.send('hipaa');
      expect(window.parent.postMessage).toHaveBeenCalledWith('hipaa', jasmine.any(String));

    });

    it('send() should post object event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');

      avMessages.send({event: 'resize', height: '45px'});

      expect(window.parent.postMessage).toHaveBeenCalledWith('{"event":"resize","height":"45px"}', jasmine.any(String));

    });

    it('onMessage() should emit string event', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: 'window:resize'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', null);

    });

    it('onMessage() should emit string event', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: '{"event":"window:resize","height":"45px"}'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', {"event":"window:resize","height":"45px"});

    });

  });

});
<commit_msg>remove comment<commit_after>/*global availity, it, inject, jasmine, module, spyOn, beforeEach, expect, describe */
describe('avMessagesProvider', function() {

  'use strict';

  var avMessagesProvider;
  var $rootScope;
  var avMessages;
  var AV_MESSAGES;

  availity.mock.providerSpecHelper();

  beforeEach(module('availity', function(_avMessagesProvider_) {

    avMessagesProvider = _avMessagesProvider_;
    avMessagesProvider.enable(false);

  }));

  beforeEach(inject(function(_$rootScope_, _avMessages_, _AV_MESSAGES_) {
    $rootScope = _$rootScope_;
    avMessages = _avMessages_;
    AV_MESSAGES = _AV_MESSAGES_;
  }));

  it('should be defined', function() {
    expect(avMessagesProvider.$get).toBeDefined();
    expect(avMessagesProvider.enable).toBeDefined();
  });

  describe('avMessages', function() {

    function testDomain(domain) {
      avMessages.domain = jasmine.createSpy().and.returnValue(domain);
      expect(avMessages.isDomain(domain)).toBeTruthy();
    }

    it('regex should validate availity.com domains', function() {

      testDomain('https://availity.com');
      testDomain('https://availity.com');
      testDomain('https://www.availity.com');
      testDomain('https://apps.availity.com');
      testDomain('https://test-apps.availity.com');
      testDomain('https://availity.net');

      testDomain('http://localhost:3000');
      testDomain('http://0.0.0.0:3000');
      testDomain('http://127.0.0.1:3000');

    });

    it('should send AV_MAXIMIZE event on init()', function() {

      spyOn(avMessages, 'send');
      avMessages.init();

      expect(avMessages.send).toHaveBeenCalledWith(AV_MESSAGES.EVENTS.AV_MAXIMIZE);

      avMessages.destroy();

    });

    beforeEach(function() {

      // Make debounce sync
      spyOn(_, 'debounce').and.callFake(function(func) {
        return function () {
          func.apply(this, arguments);
        };
      });

    });

    it('should send AV_RESIZE when window is resized', function() {

      avMessages.init();

      spyOn(avMessages, 'onResize').and.callThrough();
      spyOn(avMessages, 'send');

      window.scroll(0, 1000);
      $(window).trigger("resize");

      expect(avMessages.onResize).toHaveBeenCalled();
      expect(avMessages.send).toHaveBeenCalled();

      var payload = avMessages.send.calls.mostRecent().args[0];
      expect(payload.event).toBe(AV_MESSAGES.EVENTS.AV_RESIZE);
      expect(payload.height).toBeDefined();

      avMessages.destroy();

    });

    it('send() should post string event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');
      avMessages.send('hipaa');
      expect(window.parent.postMessage).toHaveBeenCalledWith('hipaa', jasmine.any(String));

    });

    it('send() should post object event', function() {

      // Can't spyOn postMessage in Phantomjs because it's marked as non-configurable
      if(/PhantomJS/.test(navigator.userAgent)) {
        return;
      }

      spyOn(window.parent, 'postMessage');

      avMessages.send({event: 'resize', height: '45px'});

      expect(window.parent.postMessage).toHaveBeenCalledWith('{"event":"resize","height":"45px"}', jasmine.any(String));

    });

    it('onMessage() should emit string event', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: 'window:resize'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', null);

    });

    it('onMessage() should emit string event', function() {

      spyOn($rootScope, '$broadcast');

      avMessages.onMessage({
        origin: 'http://localhost:9876',
        data: '{"event":"window:resize","height":"45px"}'
      });

      expect($rootScope.$broadcast).toHaveBeenCalledWith('window:resize', {"event":"window:resize","height":"45px"});

    });

  });

});
<|endoftext|>


<commit_before>let index = 0;
const logEntries = [];
for (let i = 0; i < 500; i++) {
  logEntries.push(null);
}

export function getLog() {
  return logEntries;
}
export function log(name, message) {
  logEntries.shift();
  logEntries.push({
    index: index++,
    date: (new Date()).toISOString(),
    level: 'log',
    name,
    message,
  });
  console.log(name + ': ' + message);
}
export function warn(message) {
  logEntries.shift();
  logEntries.push({
    index: index++,
    date: (new Date()).toISOString(),
    level: 'warn',
    message,
  });
  console.warn(message);
}
export function error(context, stack) {
  logEntries.shift();
  logEntries.push({
    index: index++,
    date: (new Date()).toISOString(),
    level: 'error',
    context,
    stack,
  });
  console.error(context + '\n' + stack);
}
<commit_msg>Only log errors for heroku<commit_after>let index = 0;
const logEntries = [];
for (let i = 0; i < 500; i++) {
  logEntries.push(null);
}

export function getLog() {
  return logEntries;
}
export function log(name, message) {
  logEntries.shift();
  logEntries.push({
    index: index++,
    date: (new Date()).toISOString(),
    level: 'log',
    name,
    message,
  });
}
export function warn(message) {
  logEntries.shift();
  logEntries.push({
    index: index++,
    date: (new Date()).toISOString(),
    level: 'warn',
    message,
  });
}
export function error(context, stack) {
  logEntries.shift();
  logEntries.push({
    index: index++,
    date: (new Date()).toISOString(),
    level: 'error',
    context,
    stack,
  });
  console.error(context + '\n' + stack);
}
<|endoftext|>


<commit_before>/* global window, document, chrome, location, history, top, XMLHttpRequest */

const port = chrome.runtime.connect();
let collapsers, options, jsonObject, jsonSelector;

function displayError(error, loc, offset) {
	const link = document.createElement("link");
	const pre = document.body.firstChild.firstChild;
	const text = pre.textContent.substring(offset);
	const range = document.createRange();
	const imgError = document.createElement("img");
	const content = document.createElement("div");
	const errorPosition = document.createElement("span");
	const container = document.createElement("div");
	const closeButton = document.createElement("div");
	const ranges = [];
	let start = 0, idx = 0, end;
	link.rel = "stylesheet";
	link.type = "text/css";
	link.href = chrome.runtime.getURL("content_error.css");
	document.head.appendChild(link);
	while (idx != -1) {
		idx = text.indexOf("\n", start);
		ranges.push(start);
		start = idx + 1;
	}
	start = ranges[loc.first_line - 1] + loc.first_column + offset;
	end = ranges[loc.last_line - 1] + loc.last_column + offset;
	range.setStart(pre, start);
	if (start == end - 1)
		range.setEnd(pre, start);
	else
		range.setEnd(pre, end);
	errorPosition.className = "error-position";
	errorPosition.id = "error-position";
	range.surroundContents(errorPosition);
	imgError.src = chrome.runtime.getURL("error.gif");
	errorPosition.insertBefore(imgError, errorPosition.firstChild);
	content.className = "content";
	closeButton.className = "close-error";
	closeButton.onclick = function () {
		content.parentElement.removeChild(content);
	};
	content.textContent = error;
	content.appendChild(closeButton);
	container.className = "container";
	container.appendChild(content);
	errorPosition.parentNode.insertBefore(container, errorPosition.nextSibling);
	location.hash = "error-position";
	history.replaceState({}, "", "#");
}

function displayUI(theme, html) {
	const baseStyleElement = document.createElement("link");
	baseStyleElement.rel = "stylesheet";
	baseStyleElement.type = "text/css";
	baseStyleElement.href = chrome.runtime.getURL("jsonview-core.css");
	document.head.appendChild(baseStyleElement);
	const userStyleElement = document.createElement("style");
	userStyleElement.appendChild(document.createTextNode(theme));
	document.head.appendChild(userStyleElement);
	document.body.innerHTML = html;
	collapsers = document.querySelectorAll("#json .collapsible .collapsible");
	const statusElement = document.createElement("div");
	statusElement.className = "status";
	const copyPathElement = document.createElement("div");
	copyPathElement.className = "copy-path";
	statusElement.appendChild(copyPathElement);
	document.body.appendChild(statusElement);
	const toolboxElement = document.createElement("div");
	toolboxElement.className = "toolbox";
	const expandElement = document.createElement("span");
	expandElement.title = "expand all";
	expandElement.innerText = "+";
	const reduceElement = document.createElement("span");
	reduceElement.title = "reduce all";
	reduceElement.innerText = "-";
	toolboxElement.appendChild(expandElement);
	toolboxElement.appendChild(reduceElement);
	document.body.appendChild(toolboxElement);
	document.body.addEventListener("click", ontoggle, false);
	document.body.addEventListener("mouseover", onmouseMove, false);
	document.body.addEventListener("click", onmouseClick, false);
	document.body.addEventListener("contextmenu", onContextMenu, false);
	expandElement.addEventListener("click", onexpand, false);
	reduceElement.addEventListener("click", onreduce, false);
	copyPathElement.addEventListener("click", function (event) {
		if (event.isTrusted === false)
			return;
		port.postMessage({
			copyPropertyPath: true,
			path: statusElement.innerText
		});
	}, false);
}

function extractData(rawText) {
	const text = rawText.trim();
	let tokens;

	function test(text) {
		return ((text.charAt(0) == "[" && text.charAt(text.length - 1) == "]") || (text.charAt(0) == "{" && text.charAt(text.length - 1) == "}"));
	}

	if (test(text))
		return {
			text: rawText,
			offset: 0
		};
	tokens = text.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/);
	if (tokens && tokens[1] && tokens[2]) {
		if (test(tokens[2].trim()))
			return {
				fnName: tokens[1],
				text: tokens[2],
				offset: rawText.indexOf(tokens[2])
			};
	}
}

function processData(data) {
	let xhr, jsonText;

	function formatToHTML(fnName, offset) {
		if (!jsonText)
			return;
		port.postMessage({
			jsonToHTML: true,
			json: jsonText,
			fnName: fnName,
			offset: offset
		});
		try {
			jsonObject = JSON.parse(jsonText);
		} catch (e) {
			// ignored
		}
	}

	if (window == top || options.injectInFrame)
		if (options.safeMethod) {
			xhr = new XMLHttpRequest();
			xhr.onreadystatechange = function () {
				if (this.readyState == 4) {
					data = extractData(this.responseText);
					if (data) {
						jsonText = data.text;
						formatToHTML(data.fnName, data.offset);
					}
				}
			};
			xhr.open("GET", document.location.href, true);
			xhr.send(null);
		} else if (data) {
			jsonText = data.text;
			formatToHTML(data.fnName, data.offset);
		}
}

function ontoggle(event) {
	const target = event.target;
	let collapsed;
	if (event.target.className == "collapser") {
		collapsed = target.parentNode.getElementsByClassName("collapsible")[0];
		if (collapsed.parentNode.classList.contains("collapsed"))
			collapsed.parentNode.classList.remove("collapsed");
		else
			collapsed.parentNode.classList.add("collapsed");
	}
}

function onexpand() {
	Array.prototype.forEach.call(collapsers, function (collapsed) {
		if (collapsed.parentNode.classList.contains("collapsed"))
			collapsed.parentNode.classList.remove("collapsed");
	});
}

function onreduce() {
	Array.prototype.forEach.call(collapsers, function (collapsed) {
		if (!collapsed.parentNode.classList.contains("collapsed"))
			collapsed.parentNode.classList.add("collapsed");
	});
}

function getParentLI(element) {
	if (element.tagName != "LI")
		while (element && element.tagName != "LI")
			element = element.parentNode;
	if (element && element.tagName == "LI")
		return element;
}

const onmouseMove = (function () {
	let hoveredLI;

	function onmouseOut() {
		const statusElement = document.querySelector(".status");
		if (hoveredLI) {
			hoveredLI.firstChild.classList.remove("hovered");
			hoveredLI = null;
			statusElement.innerText = "";
			jsonSelector = [];
		}
	}

	return function (event) {
		if (event.isTrusted === false)
			return;
		const statusElement = document.querySelector(".status");
		let str = "";
		let element = getParentLI(event.target);
		if (element) {
			jsonSelector = [];
			if (hoveredLI)
				hoveredLI.firstChild.classList.remove("hovered");
			hoveredLI = element;
			element.firstChild.classList.add("hovered");
			do {
				if (element.parentNode.classList.contains("array")) {
					const index = [].indexOf.call(element.parentNode.children, element);
					str = "[" + index + "]" + str;
					jsonSelector.unshift(index);
				}
				if (element.parentNode.classList.contains("obj")) {
					const key = element.firstChild.firstChild.innerText;
					str = "." + key + str;
					jsonSelector.unshift(key);
				}
				element = element.parentNode.parentNode.parentNode;
			} while (element.tagName == "LI");
			if (str.charAt(0) == ".")
				str = str.substring(1);
			statusElement.innerText = str;
			return;
		}
		onmouseOut();
	};
})();

let selectedLI;

function onmouseClick(event) {
	if (selectedLI)
		selectedLI.firstChild.classList.remove("selected");
	selectedLI = getParentLI(event.target);
	if (selectedLI) {
		selectedLI.firstChild.classList.add("selected");
	}
}

function onContextMenu(event) {
	if (event.isTrusted === false)
		return;
	const currentLI = getParentLI(event.target);
	const statusElement = document.querySelector(".status");
	if (currentLI) {
		let value = jsonObject;
		jsonSelector.forEach(function (idx) {
			value = value[idx];
		});
		port.postMessage({
			copyPropertyPath: true,
			path: statusElement.innerText,
			value: typeof value == "object" ? JSON.stringify(value) : value
		});
	}
}

function init(data) {
	port.onMessage.addListener(function (msg) {
		if (msg.oninit) {
			options = msg.options;
			processData(data);
		}
		if (msg.onjsonToHTML && msg.html) {
			displayUI(msg.theme, msg.html);
		}
		if (msg.ongetError) {
			displayError(msg.error, msg.loc, msg.offset);
		}
	});
	port.postMessage({
		init: true
	});
}

function load() {
	if (document.body && (document.body.childNodes[0] && document.body.childNodes[0].tagName == "PRE" || document.body.children.length == 0)) {
		const child = document.body.children.length ? document.body.childNodes[0] : document.body;
		const data = extractData(child.innerText);
		if (data)
			init(data);
	}
}

load();
<commit_msg>call forEach directly<commit_after>/* global window, document, chrome, location, history, top, XMLHttpRequest */

const port = chrome.runtime.connect();
let collapsers, options, jsonObject, jsonSelector;

function displayError(error, loc, offset) {
	const link = document.createElement("link");
	const pre = document.body.firstChild.firstChild;
	const text = pre.textContent.substring(offset);
	const range = document.createRange();
	const imgError = document.createElement("img");
	const content = document.createElement("div");
	const errorPosition = document.createElement("span");
	const container = document.createElement("div");
	const closeButton = document.createElement("div");
	const ranges = [];
	let start = 0, idx = 0, end;
	link.rel = "stylesheet";
	link.type = "text/css";
	link.href = chrome.runtime.getURL("content_error.css");
	document.head.appendChild(link);
	while (idx != -1) {
		idx = text.indexOf("\n", start);
		ranges.push(start);
		start = idx + 1;
	}
	start = ranges[loc.first_line - 1] + loc.first_column + offset;
	end = ranges[loc.last_line - 1] + loc.last_column + offset;
	range.setStart(pre, start);
	if (start == end - 1)
		range.setEnd(pre, start);
	else
		range.setEnd(pre, end);
	errorPosition.className = "error-position";
	errorPosition.id = "error-position";
	range.surroundContents(errorPosition);
	imgError.src = chrome.runtime.getURL("error.gif");
	errorPosition.insertBefore(imgError, errorPosition.firstChild);
	content.className = "content";
	closeButton.className = "close-error";
	closeButton.onclick = function () {
		content.parentElement.removeChild(content);
	};
	content.textContent = error;
	content.appendChild(closeButton);
	container.className = "container";
	container.appendChild(content);
	errorPosition.parentNode.insertBefore(container, errorPosition.nextSibling);
	location.hash = "error-position";
	history.replaceState({}, "", "#");
}

function displayUI(theme, html) {
	const baseStyleElement = document.createElement("link");
	baseStyleElement.rel = "stylesheet";
	baseStyleElement.type = "text/css";
	baseStyleElement.href = chrome.runtime.getURL("jsonview-core.css");
	document.head.appendChild(baseStyleElement);
	const userStyleElement = document.createElement("style");
	userStyleElement.appendChild(document.createTextNode(theme));
	document.head.appendChild(userStyleElement);
	document.body.innerHTML = html;
	collapsers = document.querySelectorAll("#json .collapsible .collapsible");
	const statusElement = document.createElement("div");
	statusElement.className = "status";
	const copyPathElement = document.createElement("div");
	copyPathElement.className = "copy-path";
	statusElement.appendChild(copyPathElement);
	document.body.appendChild(statusElement);
	const toolboxElement = document.createElement("div");
	toolboxElement.className = "toolbox";
	const expandElement = document.createElement("span");
	expandElement.title = "expand all";
	expandElement.innerText = "+";
	const reduceElement = document.createElement("span");
	reduceElement.title = "reduce all";
	reduceElement.innerText = "-";
	toolboxElement.appendChild(expandElement);
	toolboxElement.appendChild(reduceElement);
	document.body.appendChild(toolboxElement);
	document.body.addEventListener("click", ontoggle, false);
	document.body.addEventListener("mouseover", onmouseMove, false);
	document.body.addEventListener("click", onmouseClick, false);
	document.body.addEventListener("contextmenu", onContextMenu, false);
	expandElement.addEventListener("click", onexpand, false);
	reduceElement.addEventListener("click", onreduce, false);
	copyPathElement.addEventListener("click", function (event) {
		if (event.isTrusted === false)
			return;
		port.postMessage({
			copyPropertyPath: true,
			path: statusElement.innerText
		});
	}, false);
}

function extractData(rawText) {
	const text = rawText.trim();
	let tokens;

	function test(text) {
		return ((text.charAt(0) == "[" && text.charAt(text.length - 1) == "]") || (text.charAt(0) == "{" && text.charAt(text.length - 1) == "}"));
	}

	if (test(text))
		return {
			text: rawText,
			offset: 0
		};
	tokens = text.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/);
	if (tokens && tokens[1] && tokens[2]) {
		if (test(tokens[2].trim()))
			return {
				fnName: tokens[1],
				text: tokens[2],
				offset: rawText.indexOf(tokens[2])
			};
	}
}

function processData(data) {
	let xhr, jsonText;

	function formatToHTML(fnName, offset) {
		if (!jsonText)
			return;
		port.postMessage({
			jsonToHTML: true,
			json: jsonText,
			fnName: fnName,
			offset: offset
		});
		try {
			jsonObject = JSON.parse(jsonText);
		} catch (e) {
			// ignored
		}
	}

	if (window == top || options.injectInFrame)
		if (options.safeMethod) {
			xhr = new XMLHttpRequest();
			xhr.onreadystatechange = function () {
				if (this.readyState == 4) {
					data = extractData(this.responseText);
					if (data) {
						jsonText = data.text;
						formatToHTML(data.fnName, data.offset);
					}
				}
			};
			xhr.open("GET", document.location.href, true);
			xhr.send(null);
		} else if (data) {
			jsonText = data.text;
			formatToHTML(data.fnName, data.offset);
		}
}

function ontoggle(event) {
	const target = event.target;
	let collapsed;
	if (event.target.className == "collapser") {
		collapsed = target.parentNode.getElementsByClassName("collapsible")[0];
		if (collapsed.parentNode.classList.contains("collapsed"))
			collapsed.parentNode.classList.remove("collapsed");
		else
			collapsed.parentNode.classList.add("collapsed");
	}
}

function onexpand() {
	collapsers.forEach(function (collapsed) {
		if (collapsed.parentNode.classList.contains("collapsed"))
			collapsed.parentNode.classList.remove("collapsed");
	});
}

function onreduce() {
	collapsers.forEach(function (collapsed) {
		if (!collapsed.parentNode.classList.contains("collapsed"))
			collapsed.parentNode.classList.add("collapsed");
	});
}

function getParentLI(element) {
	if (element.tagName != "LI")
		while (element && element.tagName != "LI")
			element = element.parentNode;
	if (element && element.tagName == "LI")
		return element;
}

const onmouseMove = (function () {
	let hoveredLI;

	function onmouseOut() {
		const statusElement = document.querySelector(".status");
		if (hoveredLI) {
			hoveredLI.firstChild.classList.remove("hovered");
			hoveredLI = null;
			statusElement.innerText = "";
			jsonSelector = [];
		}
	}

	return function (event) {
		if (event.isTrusted === false)
			return;
		const statusElement = document.querySelector(".status");
		let str = "";
		let element = getParentLI(event.target);
		if (element) {
			jsonSelector = [];
			if (hoveredLI)
				hoveredLI.firstChild.classList.remove("hovered");
			hoveredLI = element;
			element.firstChild.classList.add("hovered");
			do {
				if (element.parentNode.classList.contains("array")) {
					const index = [].indexOf.call(element.parentNode.children, element);
					str = "[" + index + "]" + str;
					jsonSelector.unshift(index);
				}
				if (element.parentNode.classList.contains("obj")) {
					const key = element.firstChild.firstChild.innerText;
					str = "." + key + str;
					jsonSelector.unshift(key);
				}
				element = element.parentNode.parentNode.parentNode;
			} while (element.tagName == "LI");
			if (str.charAt(0) == ".")
				str = str.substring(1);
			statusElement.innerText = str;
			return;
		}
		onmouseOut();
	};
})();

let selectedLI;

function onmouseClick(event) {
	if (selectedLI)
		selectedLI.firstChild.classList.remove("selected");
	selectedLI = getParentLI(event.target);
	if (selectedLI) {
		selectedLI.firstChild.classList.add("selected");
	}
}

function onContextMenu(event) {
	if (event.isTrusted === false)
		return;
	const currentLI = getParentLI(event.target);
	const statusElement = document.querySelector(".status");
	if (currentLI) {
		let value = jsonObject;
		jsonSelector.forEach(function (idx) {
			value = value[idx];
		});
		port.postMessage({
			copyPropertyPath: true,
			path: statusElement.innerText,
			value: typeof value == "object" ? JSON.stringify(value) : value
		});
	}
}

function init(data) {
	port.onMessage.addListener(function (msg) {
		if (msg.oninit) {
			options = msg.options;
			processData(data);
		}
		if (msg.onjsonToHTML && msg.html) {
			displayUI(msg.theme, msg.html);
		}
		if (msg.ongetError) {
			displayError(msg.error, msg.loc, msg.offset);
		}
	});
	port.postMessage({
		init: true
	});
}

function load() {
	if (document.body && (document.body.childNodes[0] && document.body.childNodes[0].tagName == "PRE" || document.body.children.length == 0)) {
		const child = document.body.children.length ? document.body.childNodes[0] : document.body;
		const data = extractData(child.innerText);
		if (data)
			init(data);
	}
}

load();
<|endoftext|>


<commit_before>var YouTyping = function (element, settings) {
	var youTyping = this;

	/******************* Internal functions *******************/

	var setupPlayerDeferred;
	var setupPlayer = function (callback) {
		setupPlayerDeferred = $.Deferred();
		logTrace('Setting Player Up...');

		var APITag = document.createElement('script');
		APITag.src = 'https://www.youtube.com/iframe_api';
		var firstScript = document.getElementsByTagName('script')[0];
		firstScript.parentNode.insertBefore(APITag, firstScript);

		return setupPlayerDeferred.promise();
	};

	// callback function used by YouTube IFrame Player API. must be global
	onYouTubeIframeAPIReady = function () {
		var settings = youTyping.settings;

		logTrace('Player API is Ready.');

		// try to hide advertisement if sandbox parameter is 'true' or not defined in URI query
		if (getParameterByName('sandbox') === 'true') {
			this.DOM.player.setAttribute('sandbox', 'allow-same-origin allow-scripts');
		}

		youTyping.player = new YT.Player('youtyping-player', {
			height: settings.height,
			width: settings.width,
			videoId: settings.videoId,
			playerVars: {
				rel: 0,
				// Wishing the best effort of hiding any information except for the video
				controls: 0,
				showinfo: 0,
				modestbranding: 1,
				wmode: 'opaque' // thanks http://stackoverflow.com/questions/6826386/
			},
			events: {
				'onReady': onPlayerReady,
				'onStateChange': onPlayerStateChange,
				'onError': onPlayerError
			}
		});
	};

	var onPlayerReady = function (event) {
		logTrace('Player is Ready.');
		setupPlayerDeferred.resolve();
	};

	var onPlayerStateChange = function (event) {
		switch (event.data) {
			case YT.PlayerState.ENDED:
				logTrace('Player Ended.');
				break;
			case YT.PlayerState.PLAYING:
				logTrace('Player Started.');
				break;
			case YT.PlayerState.PAUSED:
				logTrace('Player Paused.');
				break;
			case YT.PlayerState.BUFFERING:
				logTrace('Player Buffering.');
				break;
			case YT.PlayerState.CUED:
				logTrace('Player Cued.');
				break;
		}
	};

	var onPlayerError = function (event) {
		// from https://developers.google.com/youtube/iframe_api_reference
		switch (event.data) {
			case 2:
				logTrace('ERROR: The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.');
				break;
			case 5:
				logTrace('ERROR: The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.');
				break;
			case 100:
				logTrace('ERROR: The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.');
				break;
			case 101:
				logTrace('ERROR: The owner of the requested video does not allow it to be played in embedded players.');
				break;
			case 150:
				logTrace('ERROR: The owner of the requested video does not allow it to be played in embedded players.');
				break;
		}
		setupPlayerDeferred.reject();
	};

	var loadXMLDeferred;
	var loadScoreXML = function () {
		var settings = youTyping.settings;

		// Initialize deferred
		loadXMLDeferred = $.Deferred();

		$.ajax({
			url: settings.score,
			type: 'get',
			datatype: 'xml',
			timeout: 1000,
			success: function (data, textStatus, jqXHR) {
				youTyping.scoreXML = $(data).find('fumen').find('item');
				logTrace('Loaded XML File.');
				computeParameters();
				loadXMLDeferred.resolve();
			},
			error: function (jqXHR, textStatus, errorThrown) {
				logTrace('ERROR: XML File Loading Failed: ' + errorThrown);
				loadXMLDeferred.reject();
			}
		});

		return loadXMLDeferred.promise();
	};

	var computeParameters = function () {
		var settings = youTyping.settings;

		var paddingRight = settings.width - settings.hitPosition + settings.noteSize + settings.screenPadding; // distance from hit line to right edge
		var paddingLeft = settings.hitPosition + settings.noteSize + settings.screenPadding; // distance from hit line to left edge

		try {
			youTyping.score = [];

			$(youTyping.scoreXML).each(function () {
				var tempItem = {
					time: parseFloat($(this).attr('time')),
					type: $(this).attr('type')
				};

				if ($(this).attr('text')) {
					tempItem.text = $(this).attr('text');
				}

				youTyping.score.push(tempItem);
			});

			// Computes emerge time and vanishing time of item.
			// This is yet a very simple way without regards for speed changes.
			youTyping.score.forEach(function (item, index) {
				item.emergeTime = (settings.speed * item.time - paddingRight) / settings.speed;
				item.vanishTime = (settings.speed * item.time + paddingLeft) / settings.speed;
			});

			logTrace('Computed score Parameters.');
		} catch (error) {
			logTrace('ERROR: Computing score Parameters Faild: ' + error);
			loadXMLDeferred.reject();
		}
	};

	/******************* properties *******************/

	this.startTime = Date.now();

	// score data
	this.scoreXML = null;
	this.score = null;

	// YouTube IFrame Player
	this.player = null;

	// default settings
	this.settings = {
		zeroEstimateSamples: 16, // integer
		videoId: 'fQ_m5VLhqNg',
		score: 'data.utfx',
		width: 1120, // pixel
		height: 630, // pixel
		hitPosition: 200, // pixel
		noteSize: 50, // pixel
		speed: 500, // pixel per second
		scoreYpos: 0.5, // ratio
		longLineHeight: 150, // pixel
		lineHeight: 120, // pixel
		screenPadding: 30 // pixel
	};

	// ZeroTime calculation
	this.zeroTime = 0;
	this.zeroTimePad = 0;
	this.currentTime = 0;
	this.estimateSamples = [];
	this.estimatedZero = 0; // exposed only for debugging
	this.zeroCallFPS = 0; // exposed only for debugging

	/******************* Methods *******************/

	this.play = function () {
		youTyping.player.playVideo();

		// Set interval to calculate `ZeroTime`

		/***************

		# What's `ZeroTime` and `ZeroCall`?

		The current time taken from YouTube API by `getCurrentTime()`
		is resoluted very roughly (about 0.2s) with a great range of errors (about 0.05s).

		It's so fatal for music game like YouTyping. So we introduced idea that calibrates
		correct playing time by taking average of measuring. That's `ZeroTime`.

		YouTyping loops to get current playing time from API (to `gotCurrentTime`)
		with enough interval time (10ms) to detect when the `getCurrentTime()` time jumped up to another value.
		And each time `gotCurrentTime` jumped (nameed `ZeroCall`),
		YouTyping assumes the time to be correct and counts backward to estimate when this video started,
		so the time is nameed `ZeroTime`. Then the current playing time of video will be calculated by `ZeroTime` and
		current time taken from browser clock (very highly resoluted as <1ms).

		***************/
		setInterval(function () {
			var gotCurrentTime = youTyping.player.getCurrentTime();
			var now = window.performance.now() || (Date.now() - youTyping.startTime);

			if (gotCurrentTime === 0) { // if playing time is zero `ZeroTime` is immediately `now`!
				youTyping.zeroTimePad = now;
			} else if (youTyping.currentTime !== gotCurrentTime) { // if Current Time jumped
				youTyping.currentTime = gotCurrentTime;
				youTyping.estimatedZero = now - youTyping.currentTime * 1000;

				// Estimated zero time is stored in estimatesamples and
				// we assume that correct zero time is average of recent
				// `zeroEstimateSamples` items of samples
				// because it contains great ranges of error.
				// We also introduced `zeroTimePad` to supress a sudden change of zeroTime.
				// It contains correct zero time and sudden-change-supressed zero time
				// will be stored in `zeroTime`.
				youTyping.estimateSamples.push(youTyping.estimatedZero);
				if (youTyping.estimateSamples.length > youTyping.settings.zeroEstimateSamples) {
					youTyping.estimateSamples.shift();
				}
				// just go hack :)
				var estimatedSum = youTyping.estimateSamples.reduce(function (previous, current) {
					return previous + current;
				});

				// `zeroTimePad` is actual estimated ZeroTime and real displayed ZeroTime is modested into `zeroTime`.
				youTyping.zeroTimePad = estimatedSum / youTyping.estimateSamples.length;

				youTyping.zeroCallFPS++;
			}
			youTyping.zeroTime = (youTyping.zeroTime - youTyping.zeroTimePad) * 0.9 + youTyping.zeroTimePad;
		}, 10);
	};

	/******************* Initialization *******************/

	// override default settings
	for (var param in settings) {
		if (this.settings[param] === undefined) {
			this.settings[param] = settings[param];
		} else if (typeof this.settings[param] === 'number') {
			this.settings[param] = parseInt(settings[param], 10);
		} else {
			this.settings[param] = settings[param];
		}
	}

	// setup DOM
	this.DOM = {
		wrap: element.css({
			width: this.settings.width + 'px',
			height: this.settings.height + 'px',
			margin: '0 auto',
			position: 'relative'
		}),

		player: $('<div/>', {
			id: 'youtyping-player'
		}).appendTo(element).css({
			width: this.settings.width + 'px',
			height: this.settings.height + 'px',
			display: 'block',
			'z-index': 0
		}),

		screen: $('<canvas/>', {
			id: 'youtyping-screen',
			'data-paper-keepalive': 'true',
			width: this.settings.width.toString(),
			height: this.settings.height.toString()
		}).appendTo(element).css({
			width: this.settings.width + 'px',
			height: this.settings.height + 'px',
			position: 'absolute',
			top: 0,
			left: 0,
			'z-index': 100
		})
	};

	// create YouTyping screen class
	this.screen = new Screen(document.getElementById('youtyping-screen'), this);

	// Initialize asynchronously
	var player = setupPlayer();
	var XML = loadScoreXML();
	var screen = $.Deferred(this.screen.setup).promise();
	$.when(
		$.when(
			XML,
			screen
		).done(this.screen.load),
		player
	).done(this.screen.ready)
	.fail(function () {
		logTrace('ERROR: Initialization Failed...');
	});
};<commit_msg>adjust zeroTimePad and zeroTime equally<commit_after>var YouTyping = function (element, settings) {
	var youTyping = this;

	/******************* Internal functions *******************/

	var setupPlayerDeferred;
	var setupPlayer = function (callback) {
		setupPlayerDeferred = $.Deferred();
		logTrace('Setting Player Up...');

		var APITag = document.createElement('script');
		APITag.src = 'https://www.youtube.com/iframe_api';
		var firstScript = document.getElementsByTagName('script')[0];
		firstScript.parentNode.insertBefore(APITag, firstScript);

		return setupPlayerDeferred.promise();
	};

	// callback function used by YouTube IFrame Player API. must be global
	onYouTubeIframeAPIReady = function () {
		var settings = youTyping.settings;

		logTrace('Player API is Ready.');

		// try to hide advertisement if sandbox parameter is 'true' or not defined in URI query
		if (getParameterByName('sandbox') === 'true') {
			this.DOM.player.setAttribute('sandbox', 'allow-same-origin allow-scripts');
		}

		youTyping.player = new YT.Player('youtyping-player', {
			height: settings.height,
			width: settings.width,
			videoId: settings.videoId,
			playerVars: {
				rel: 0,
				// Wishing the best effort of hiding any information except for the video
				controls: 0,
				showinfo: 0,
				modestbranding: 1,
				wmode: 'opaque' // thanks http://stackoverflow.com/questions/6826386/
			},
			events: {
				'onReady': onPlayerReady,
				'onStateChange': onPlayerStateChange,
				'onError': onPlayerError
			}
		});
	};

	var onPlayerReady = function (event) {
		logTrace('Player is Ready.');
		setupPlayerDeferred.resolve();
	};

	var onPlayerStateChange = function (event) {
		switch (event.data) {
			case YT.PlayerState.ENDED:
				logTrace('Player Ended.');
				break;
			case YT.PlayerState.PLAYING:
				logTrace('Player Started.');
				break;
			case YT.PlayerState.PAUSED:
				logTrace('Player Paused.');
				break;
			case YT.PlayerState.BUFFERING:
				logTrace('Player Buffering.');
				break;
			case YT.PlayerState.CUED:
				logTrace('Player Cued.');
				break;
		}
	};

	var onPlayerError = function (event) {
		// from https://developers.google.com/youtube/iframe_api_reference
		switch (event.data) {
			case 2:
				logTrace('ERROR: The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.');
				break;
			case 5:
				logTrace('ERROR: The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.');
				break;
			case 100:
				logTrace('ERROR: The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.');
				break;
			case 101:
				logTrace('ERROR: The owner of the requested video does not allow it to be played in embedded players.');
				break;
			case 150:
				logTrace('ERROR: The owner of the requested video does not allow it to be played in embedded players.');
				break;
		}
		setupPlayerDeferred.reject();
	};

	var loadXMLDeferred;
	var loadScoreXML = function () {
		var settings = youTyping.settings;

		// Initialize deferred
		loadXMLDeferred = $.Deferred();

		$.ajax({
			url: settings.score,
			type: 'get',
			datatype: 'xml',
			timeout: 1000,
			success: function (data, textStatus, jqXHR) {
				youTyping.scoreXML = $(data).find('fumen').find('item');
				logTrace('Loaded XML File.');
				computeParameters();
				loadXMLDeferred.resolve();
			},
			error: function (jqXHR, textStatus, errorThrown) {
				logTrace('ERROR: XML File Loading Failed: ' + errorThrown);
				loadXMLDeferred.reject();
			}
		});

		return loadXMLDeferred.promise();
	};

	var computeParameters = function () {
		var settings = youTyping.settings;

		var paddingRight = settings.width - settings.hitPosition + settings.noteSize + settings.screenPadding; // distance from hit line to right edge
		var paddingLeft = settings.hitPosition + settings.noteSize + settings.screenPadding; // distance from hit line to left edge

		try {
			youTyping.score = [];

			$(youTyping.scoreXML).each(function () {
				var tempItem = {
					time: parseFloat($(this).attr('time')),
					type: $(this).attr('type')
				};

				if ($(this).attr('text')) {
					tempItem.text = $(this).attr('text');
				}

				youTyping.score.push(tempItem);
			});

			// Computes emerge time and vanishing time of item.
			// This is yet a very simple way without regards for speed changes.
			youTyping.score.forEach(function (item, index) {
				item.emergeTime = (settings.speed * item.time - paddingRight) / settings.speed;
				item.vanishTime = (settings.speed * item.time + paddingLeft) / settings.speed;
			});

			logTrace('Computed score Parameters.');
		} catch (error) {
			logTrace('ERROR: Computing score Parameters Faild: ' + error);
			loadXMLDeferred.reject();
		}
	};

	/******************* properties *******************/

	this.startTime = Date.now();

	// score data
	this.scoreXML = null;
	this.score = null;

	// YouTube Iframe Player
	this.player = null;

	// default settings
	this.settings = {
		zeroEstimateSamples: 16, // integer
		videoId: 'fQ_m5VLhqNg',
		score: 'data.utfx',
		width: 1120, // pixel
		height: 630, // pixel
		hitPosition: 200, // pixel
		noteSize: 50, // pixel
		speed: 500, // pixel per second
		scoreYpos: 0.5, // ratio
		longLineHeight: 150, // pixel
		lineHeight: 120, // pixel
		screenPadding: 30 // pixel
	};

	// ZeroTime calculation
	this.zeroTime = 0;
	this.zeroTimePad = 0;
	this.currentTime = 0;
	this.estimateSamples = [];
	this.estimatedZero = 0; // exposed only for debugging
	this.zeroCallFPS = 0; // exposed only for debugging

	/******************* Methods *******************/

	this.play = function () {
		youTyping.player.playVideo();

		// Set interval to calculate `ZeroTime`

		/***************

		# What's `ZeroTime` and `ZeroCall`?

		The current time taken from YouTube API by `getCurrentTime()`
		is resoluted very roughly (about 0.2s) with a great range of errors (about 0.05s).

		It's so fatal for music game like YouTyping. So we introduced idea that calibrates
		correct playing time by taking average of measuring. That's `ZeroTime`.

		YouTyping loops to get current playing time from API (to `gotCurrentTime`)
		with enough interval time (10ms) to detect when the `getCurrentTime()` time jumped up to another value.
		And each time `gotCurrentTime` jumped (nameed `ZeroCall`),
		YouTyping assumes the time to be correct and counts backward to estimate when this video started,
		so the time is nameed `ZeroTime`. Then the current playing time of video will be calculated by `ZeroTime` and
		current time taken from browser clock (very highly resoluted as <1ms).

		***************/
		setInterval(function () {
			var gotCurrentTime = youTyping.player.getCurrentTime();
			var now = window.performance.now() || (Date.now() - youTyping.startTime);

			if (gotCurrentTime === 0) { // if playing time is zero `ZeroTime` is immediately `now`!
				youTyping.zeroTimePad = now;
				youTyping.zeroTime = now;
			} else if (youTyping.currentTime !== gotCurrentTime) { // if Current Time jumped
				youTyping.currentTime = gotCurrentTime;
				youTyping.estimatedZero = now - youTyping.currentTime * 1000;

				// Estimated zero time is stored in estimatesamples and
				// we assume that correct zero time is average of recent
				// `zeroEstimateSamples` items of samples
				// because it contains great ranges of error.
				// We also introduced `zeroTimePad` to supress a sudden change of zeroTime.
				// It contains correct zero time and sudden-change-supressed zero time
				// will be stored in `zeroTime`.
				youTyping.estimateSamples.push(youTyping.estimatedZero);
				if (youTyping.estimateSamples.length > youTyping.settings.zeroEstimateSamples) {
					youTyping.estimateSamples.shift();
				}
				// just go hack :)
				var estimatedSum = youTyping.estimateSamples.reduce(function (previous, current) {
					return previous + current;
				});

				// `zeroTimePad` is actual estimated ZeroTime and real displayed ZeroTime is modested into `zeroTime`.
				youTyping.zeroTimePad = estimatedSum / youTyping.estimateSamples.length;

				youTyping.zeroCallFPS++;
			}
			youTyping.zeroTime = (youTyping.zeroTime - youTyping.zeroTimePad) * 0.9 + youTyping.zeroTimePad;
		}, 10);
	};

	/******************* Initialization *******************/

	// override default settings
	for (var param in settings) {
		if (this.settings[param] === undefined) {
			this.settings[param] = settings[param];
		} else if (typeof this.settings[param] === 'number') {
			this.settings[param] = parseInt(settings[param], 10);
		} else {
			this.settings[param] = settings[param];
		}
	}

	// setup DOM
	this.DOM = {
		wrap: element.css({
			width: this.settings.width + 'px',
			height: this.settings.height + 'px',
			margin: '0 auto',
			position: 'relative'
		}),

		player: $('<div/>', {
			id: 'youtyping-player'
		}).appendTo(element).css({
			width: this.settings.width + 'px',
			height: this.settings.height + 'px',
			display: 'block',
			'z-index': 0
		}),

		screen: $('<canvas/>', {
			id: 'youtyping-screen',
			'data-paper-keepalive': 'true',
			width: this.settings.width.toString(),
			height: this.settings.height.toString()
		}).appendTo(element).css({
			width: this.settings.width + 'px',
			height: this.settings.height + 'px',
			position: 'absolute',
			top: 0,
			left: 0,
			'z-index': 100
		})
	};

	// create YouTyping screen class
	this.screen = new Screen(document.getElementById('youtyping-screen'), this);

	// Initialize asynchronously
	var player = setupPlayer();
	var XML = loadScoreXML();
	var screen = $.Deferred(this.screen.setup).promise();
	$.when(
		$.when(
			XML,
			screen
		).done(this.screen.load),
		player
	).done(this.screen.ready)
	.fail(function () {
		logTrace('ERROR: Initialization Failed...');
	});
};<|endoftext|>