<commit_before>if (typeof module !== 'undefined')
	require('${basedir}/target/src/javascript/mqttws31n.js');
var testServer = "${test.server}";
var testPort = parseInt("${test.server.port}");
var testPath = "${test.server.path}";

var genStr = function(str){
	var time = new Date();
	return str + '-' + time.getTime();
};

describe('BasicTest', function() {
	//var client = null;
	var clientId = this.description;
	//console.log('...',this.description);
	var connected = false;
	var failure = false;
	var disconnectError = null;
	var disconnectErrorMsg = null;

	var subscribed = false;
	var isMessageReceived = false;
	var isMessageDelivered = false;
	var strTopic = '/World';
	var strMessageReceived = '';
	var strMessageSend = 'Hello';
	var strTopicReceived = '';

	function onConnectSuccess(responseObj) {
		//console.log("connected %s",responseObj);
		connected = true;
	};

	function onConnectionLost(err) {
		connected = false;
		if(err){
			disconnectError = err.errorCode;
			disconnectErrorMsg = err.errorMessage;
		}
		console.log("connection lost. ErrorCode: %s, ErrorMsg: %s",disconnectError,disconnectErrorMsg);
	};

	function onConnectFailure(err){
		connected = false;
		console.log('Connect fail. ErrorCode: %s, ErrorMsg: %s',err.errCode,err.errorMessage);
	};

	function onSubscribeSuccess() {
		subscribed = true;
		//console.log("subscribed",subscribed);
	};

	function onSubscribeFailure(err) {
		subscribed = false;
		console.log("subscrib fail. ErrorCode: %s, ErrorMsg: %s",err.errCode,err.errorMessage);
	};
	function onUnsubscribeSuccess() {
		subscribed = false;
		//console.log("unsubscribed",subscribed);
	};

	function messageDelivered(response) {
		console.log("messageDelivered. topic:%s, duplicate:%s, payloadString:%s,qos:%s,retained:%s",response.destinationName,response.duplicate,response.payloadString,response.qos,response.retained);
		isMessageDelivered = true;
		//reponse.invocationContext.onMessageArrived = null;
	};

	function messageArrived(message) {
		console.log("messageArrived.",'topic:',message.destinationName,' ;content:',message.payloadString);
		isMessageReceived = true;
		strMessageReceived = message.payloadString;
		strTopicReceived = message.destinationName;

		//reponse.invocationContext.onMessageArrived = null;
	};

	beforeEach(function(){
		connected = false;
		failure = false;
		disconnectError = null;
		disconnectErrorMsg = null;
		//if(!client){
		//	client = new Messaging.Client(testServer, testPort, clientId);
		//}
	});

	/*afterEach(function(){
		if(client){
			client.disconnect();
		}
	});
	*/
	it('it should create and connect and disconnect to a server.', function() {
		var client = new Messaging.Client(testServer, testPort, genStr(clientId));
		client.onConnectionLost = onConnectionLost;
		expect(client).not.toBe(null);

		console.log('Connecting...');
		runs(function() {
			client.connect({onSuccess:onConnectSuccess});
		});
		waitsFor(function() {
			return connected;
		}, "the client should connect", 2000);
		runs(function() {
			expect(connected).toBe(true);
		});

		console.log('Disconnecting...');
		runs(function() {
			client.disconnect();
		});
		waitsFor(function() {
			return (connected==false);
		}, "the client should disconnect", 2000);
		runs(function() {
			expect(connected).not.toBe(true);
		});

		console.log('Re-Connecting...');
		runs(function() {
			client.connect({onSuccess:onConnectSuccess});
		});

		waitsFor(function() {
			return connected;
		}, "the client should re-connect", 2000);

		runs(function() {
			expect(connected).toBe(true);
		});

	});

	it('it should connect to a list of server(HA connection).',function(){
		var defaultServer = "m2m.eclipse.org";
		var defaultPort = 1883;
		var arrHosts = ['m2m.eclipse.org',"localhost",'m2m.eclipse.org',];
		var arrPorts = [1882,1883,1883];

		var client = new Messaging.Client(defaultServer, defaultPort,genStr(clientId) );
		client.onConnectionLost = onConnectionLost;
		expect(client).not.toBe(null);

		console.log('should connect to a available server from list');
		runs(function() {
			client.connect({
				onSuccess : onConnectSuccess,
				onFailure : onConnectFailure,
				hosts : arrHosts,
				ports : arrPorts
			});
		});

		waitsFor(function() {
			return connected;
		}, "the client should connect", 2000);

		runs(function() {
			expect(connected).toBe(true);
			//client.disconnect();
		});

	});

	it('it should publish and subscribe.',function(){

		var client = new Messaging.Client(testServer, testPort, genStr(clientId));
		client.onMessageArrived = messageArrived;
		client.onMessageDelivered = messageDelivered;

		runs(function() {
			client.connect({onSuccess:onConnectSuccess,onFailure:onConnectFailure});
		});
		waitsFor(function() {
			return connected;
		}, "the client should connect", 2000);
		runs(function() {
			expect(connected).toBe(true);
		});

		console.log('Subscribe a topic...');
		runs(function() {
			client.subscribe(strTopic, {onSuccess:onSubscribeSuccess,onFailure:onSubscribeFailure});
		});

		waitsFor(function() {
			return subscribed;
		}, "the client should subscribe", 2000);

		runs(function() {
			expect(subscribed).toBe(true);
		});

		console.log('Send and receive message...');
		runs(function() {
			var message = new Messaging.Message(strMessageSend);
			message.destinationName = strTopic;
			client.send(message);
		});
		waitsFor(function() {
			return isMessageReceived;
		}, "the client should send and receive a message", 2000);
		runs(function() {
			//to do Check message sent
			expect(isMessageDelivered).toBe(true);
			//Check msg received
			expect(isMessageReceived).toBe(true);
			//Check message
			expect(strMessageReceived).toEqual(strMessageSend);
			//Check topic
			expect(strTopicReceived).toEqual(strTopic);

			//disconnect
			//client.disconnect();

		});

		console.log('Unsubscribe a topic...');
		runs(function() {
			client.unsubscribe(strTopic, {onSuccess:onUnsubscribeSuccess});
		});
	    waitsFor(function() {
			return !subscribed;
		}, "the client should subscribe", 2000);
		runs(function() {
			expect(subscribed).toBe(false);
			//disconnect
			//client.disconnect();
		});

		//should not receive a message after unsubscribe
		runs(function() {
			//console.log('isMessageReceived',isMessageReceived);
			isMessageDelivered = false;
			isMessageReceived = false;
			strMessageReceived = '';
			strTopicReceived = '';

			var message = new Messaging.Message(genStr(strMessageSend));
			message.destinationName = strTopic;
			client.send(message);
		})
		waitsFor(function() {
			return isMessageDelivered;
		}, "the client should send and receive a message", 2000);

		runs(function() {
			//to do Check message sent
			expect(isMessageDelivered).toBe(true);
			//Check msg received
			expect(isMessageReceived).toBe(false);
			//Check message
			expect(strMessageReceived).toEqual('');
			//Check topic
			expect(strTopicReceived).toEqual('');

			//disconnect
			//client.disconnect();

		})
	});

	it('check message properties.',function(){
		var strMsg = 'test Msg';
		var strDes = '/test';
		var message = new Messaging.Message(strMsg);
		message.destinationName = strDes;

		console.log('Check default for message with payload.');
		expect(message.qos).toBe(0);
		expect(message.duplicate).toBe(false);
		expect(message.retained).toBe(false);
		expect(message.payloadString).toEqual(strMsg);
		expect(message.payloadBytes.length).toBeGreaterThan(0);
		expect(message.destinationName).toEqual(strDes);

		console.log('Check empty msg to throw error');
		expect(function(){
			var empMsg = new Messaging.Message();
		}).toThrow();

		console.log('Check message qos');
		message.qos = 0;
		expect(message.qos).toBe(0);
		message.qos = 1;
		expect(message.qos).toBe(1);
		message.qos = 2;
		expect(message.qos).toBe(2);

		//illegal argument exception
		expect(function(){
			message.qos = -1;
		}).toThrow();
		expect(function(){
			message.qos = 1;
		}).not.toThrow();

		console.log('Check payload');
		var strPayload = 'payload is a string';
		message.payloadString = strPayload;
		console.log('not allowed to set payload');
		expect(message.payloadString).not.toEqual(strPayload);

		console.log('Check retained');
		message.retained = false;
		expect(message.retained).toBe(false);
		message.retained = true;
		expect(message.retained).toBe(true);

		console.log('Check duplicate');
		message.duplicate = false;
		expect(message.duplicate).toBe(false);
		message.duplicate = true;
		expect(message.duplicate).toBe(true);

		//to do , check payload
		/*
		var buffer = new ArrayBuffer(4);
		var uintArr = new Uint8Array(buffer);
		dataView = new DataView(buffer);
		dataView.setInt32(0,0x48656c6c);
		//dataView.setInt
		console.log(dataView.getInt32(0).toString(16));
		//var arrbufPayload = new ArrayBuffer
		var msg = new Messaging.Message(buffer);
		console.log(msg.payloadBytes,msg.payloadString);
		*/
	});

});

	<commit_msg>eclipse environment issues<commit_after>if (typeof module !== 'undefined')
	require('${basedir}/target/src/javascript/mqttws31n.js');
var testServer = "${test.server}";
var testPort = parseInt("${test.server.port}");
var testPath = "${test.server.path}";

var genStr = function(str){
	var time = new Date();
	return str + '-' + time.getTime();
};

describe('BasicTest', function() {
	//var client = null;
	var clientId = this.description;
	//console.log('...',this.description);
	var connected = false;
	var failure = false;
	var disconnectError = null;
	var disconnectErrorMsg = null;

	var subscribed = false;
	var isMessageReceived = false;
	var isMessageDelivered = false;
	var strTopic = '/World';
	var strMessageReceived = '';
	var strMessageSend = 'Hello';
	var strTopicReceived = '';

	function onConnectSuccess(responseObj) {
		//console.log("connected %s",responseObj);
		connected = true;
	};

	function onConnectionLost(err) {
		connected = false;
		if(err){
			disconnectError = err.errorCode;
			disconnectErrorMsg = err.errorMessage;
		}
		console.log("connection lost. ErrorCode: %s, ErrorMsg: %s",disconnectError,disconnectErrorMsg);
	};

	function onConnectFailure(err){
		connected = false;
		console.log('Connect fail. ErrorCode: %s, ErrorMsg: %s',err.errCode,err.errorMessage);
	};

	function onSubscribeSuccess() {
		subscribed = true;
		//console.log("subscribed",subscribed);
	};

	function onSubscribeFailure(err) {
		subscribed = false;
		console.log("subscrib fail. ErrorCode: %s, ErrorMsg: %s",err.errCode,err.errorMessage);
	};
	function onUnsubscribeSuccess() {
		subscribed = false;
		//console.log("unsubscribed",subscribed);
	};

	function messageDelivered(response) {
		console.log("messageDelivered. topic:%s, duplicate:%s, payloadString:%s,qos:%s,retained:%s",response.destinationName,response.duplicate,response.payloadString,response.qos,response.retained);
		isMessageDelivered = true;
		//reponse.invocationContext.onMessageArrived = null;
	};

	function messageArrived(message) {
		console.log("messageArrived.",'topic:',message.destinationName,' ;content:',message.payloadString);
		isMessageReceived = true;
		strMessageReceived = message.payloadString;
		strTopicReceived = message.destinationName;

		//reponse.invocationContext.onMessageArrived = null;
	};

	beforeEach(function(){
		connected = false;
		failure = false;
		disconnectError = null;
		disconnectErrorMsg = null;
		//if(!client){
		//	client = new Messaging.Client(testServer, testPort, clientId);
		//}
	});

	/*afterEach(function(){
		if(client){
			client.disconnect();
		}
	});
	*/
	it('it should create and connect and disconnect to a server.', function() {
		var client = new Messaging.Client(testServer, testPort, genStr(clientId));
		client.onConnectionLost = onConnectionLost;
		expect(client).not.toBe(null);

		console.log('Connecting...');
		runs(function() {
			client.connect({onSuccess:onConnectSuccess});
		});
		waitsFor(function() {
			return connected;
		}, "the client should connect", 2000);
		runs(function() {
			expect(connected).toBe(true);
		});

		console.log('Disconnecting...');
		runs(function() {
			client.disconnect();
		});
		waitsFor(function() {
			return (connected==false);
		}, "the client should disconnect", 2000);
		runs(function() {
			expect(connected).not.toBe(true);
		});

		console.log('Re-Connecting...');
		runs(function() {
			client.connect({onSuccess:onConnectSuccess});
		});

		waitsFor(function() {
			return connected;
		}, "the client should re-connect", 2000);

		runs(function() {
			expect(connected).toBe(true);
		});

	});

	it('it should connect to a list of server(HA connection).',function(){
		var defaultServer = "m2m.eclipse.org";
		var defaultPort = 1883;
		var arrHosts = ["localhost",'m2m.eclipse.org',];
		var arrPorts = [1883,1883];

		var client = new Messaging.Client(defaultServer, defaultPort,genStr(clientId) );
		client.onConnectionLost = onConnectionLost;
		expect(client).not.toBe(null);

		console.log('should connect to a available server from list');
		runs(function() {
			client.connect({
				onSuccess : onConnectSuccess,
				onFailure : onConnectFailure,
				hosts : arrHosts,
				ports : arrPorts
			});
		});

		waitsFor(function() {
			return connected;
		}, "the client should connect", 2000);

		runs(function() {
			expect(connected).toBe(true);
			//client.disconnect();
		});

	});

	it('it should publish and subscribe.',function(){

		var client = new Messaging.Client(testServer, testPort, genStr(clientId));
		client.onMessageArrived = messageArrived;
		client.onMessageDelivered = messageDelivered;

		runs(function() {
			client.connect({onSuccess:onConnectSuccess,onFailure:onConnectFailure});
		});
		waitsFor(function() {
			return connected;
		}, "the client should connect", 2000);
		runs(function() {
			expect(connected).toBe(true);
		});

		console.log('Subscribe a topic...');
		runs(function() {
			client.subscribe(strTopic, {onSuccess:onSubscribeSuccess,onFailure:onSubscribeFailure});
		});

		waitsFor(function() {
			return subscribed;
		}, "the client should subscribe", 2000);

		runs(function() {
			expect(subscribed).toBe(true);
		});

		console.log('Send and receive message...');
		runs(function() {
			var message = new Messaging.Message(strMessageSend);
			message.destinationName = strTopic;
			client.send(message);
		});
		waitsFor(function() {
			return isMessageReceived;
		}, "the client should send and receive a message", 2000);
		runs(function() {
			//to do Check message sent
			expect(isMessageDelivered).toBe(true);
			//Check msg received
			expect(isMessageReceived).toBe(true);
			//Check message
			expect(strMessageReceived).toEqual(strMessageSend);
			//Check topic
			expect(strTopicReceived).toEqual(strTopic);

			//disconnect
			//client.disconnect();

		});

		console.log('Unsubscribe a topic...');
		runs(function() {
			client.unsubscribe(strTopic, {onSuccess:onUnsubscribeSuccess});
		});
	    waitsFor(function() {
			return !subscribed;
		}, "the client should subscribe", 2000);
		runs(function() {
			expect(subscribed).toBe(false);
			//disconnect
			//client.disconnect();
		});

		//should not receive a message after unsubscribe
		runs(function() {
			//console.log('isMessageReceived',isMessageReceived);
			isMessageDelivered = false;
			isMessageReceived = false;
			strMessageReceived = '';
			strTopicReceived = '';

			var message = new Messaging.Message(genStr(strMessageSend));
			message.destinationName = strTopic;
			client.send(message);
		})
		waitsFor(function() {
			return isMessageDelivered;
		}, "the client should send and receive a message", 2000);

		runs(function() {
			//to do Check message sent
			expect(isMessageDelivered).toBe(true);
			//Check msg received
			expect(isMessageReceived).toBe(false);
			//Check message
			expect(strMessageReceived).toEqual('');
			//Check topic
			expect(strTopicReceived).toEqual('');

			//disconnect
			//client.disconnect();

		})
	});

	it('check message properties.',function(){
		var strMsg = 'test Msg';
		var strDes = '/test';
		var message = new Messaging.Message(strMsg);
		message.destinationName = strDes;

		console.log('Check default for message with payload.');
		expect(message.qos).toBe(0);
		expect(message.duplicate).toBe(false);
		expect(message.retained).toBe(false);
		expect(message.payloadString).toEqual(strMsg);
		expect(message.payloadBytes.length).toBeGreaterThan(0);
		expect(message.destinationName).toEqual(strDes);

		console.log('Check empty msg to throw error');
		expect(function(){
			var empMsg = new Messaging.Message();
		}).toThrow();

		console.log('Check message qos');
		message.qos = 0;
		expect(message.qos).toBe(0);
		message.qos = 1;
		expect(message.qos).toBe(1);
		message.qos = 2;
		expect(message.qos).toBe(2);

		//illegal argument exception
		expect(function(){
			message.qos = -1;
		}).toThrow();
		expect(function(){
			message.qos = 1;
		}).not.toThrow();

		console.log('Check payload');
		var strPayload = 'payload is a string';
		message.payloadString = strPayload;
		console.log('not allowed to set payload');
		expect(message.payloadString).not.toEqual(strPayload);

		console.log('Check retained');
		message.retained = false;
		expect(message.retained).toBe(false);
		message.retained = true;
		expect(message.retained).toBe(true);

		console.log('Check duplicate');
		message.duplicate = false;
		expect(message.duplicate).toBe(false);
		message.duplicate = true;
		expect(message.duplicate).toBe(true);

		//to do , check payload
		/*
		var buffer = new ArrayBuffer(4);
		var uintArr = new Uint8Array(buffer);
		dataView = new DataView(buffer);
		dataView.setInt32(0,0x48656c6c);
		//dataView.setInt
		console.log(dataView.getInt32(0).toString(16));
		//var arrbufPayload = new ArrayBuffer
		var msg = new Messaging.Message(buffer);
		console.log(msg.payloadBytes,msg.payloadString);
		*/
	});

});

	<|endoftext|>


<commit_before>import qs from 'query-string';

import fluxSdkWrapper from 'flux-sdk-common';
import { handleCredentials } from 'flux-sdk-common/lib/utils/open-id-connect';

const emptyBody = JSON.stringify(null);

function parseUrl(url) {
  // https://gist.github.com/jlong/2428561
  const anchor = document.createElement('a');
  anchor.href = url;
  return {
    href: anchor.href,
    protocol: anchor.protocol,
    hostname: anchor.hostname,
    port: anchor.port,
    pathname: anchor.pathname,
    search: anchor.search,
    hash: anchor.hash,
    host: anchor.host,
    query: qs.parse(anchor.search),
  };
}

function scriptFetch(path, options = {}) {
  const { headers, method, body } = options;
  const requestOptions = {
    muteHttpExceptions: false,
    headers: headers || {},
    method: method || 'get',
    payload: body || emptyBody,
    contentType: headers['Content-Type'] || headers['content-type'] || 'application/json',
  };

  return new Promise((resolve, reject) => {
    google.script.run
      .withSuccessHandler(resolve)
      .withFailureHandler(reject)
      .makeURLFetchAppCall(path, requestOptions);
  })
    .then(response => {
      const responseHeaders = response.headers;
      const responseBody = response.body;
      return {
        headers: {
          has: header => !!responseHeaders[header],
          get: header => responseHeaders[header] || '',
        },
        text: () => Promise.resolve(responseBody),
        json: () => Promise.resolve(JSON.parse(responseBody)),
      };
    });
}

const WrappedFluxSdk = fluxSdkWrapper({
  parseUrl,
  WebSocket,
  base64Encode: btoa,
  base64Decode: atob,
  parseQuery: qs.parse,
  stringifyQuery: qs.stringify,
  fetch: scriptFetch,
});

function FluxSdk(clientId, options = {}) {
  return new WrappedFluxSdk(clientId, { ...options, implicit: true });
}

FluxSdk.handleCredentials = handleCredentials;

module.exports = FluxSdk;
<commit_msg>Fix cell.fetch and cell.update in Apps Script<commit_after>import qs from 'query-string';

import fluxSdkWrapper from 'flux-sdk-common';
import { handleCredentials } from 'flux-sdk-common/lib/utils/open-id-connect';

function parseUrl(url) {
  // https://gist.github.com/jlong/2428561
  const anchor = document.createElement('a');
  anchor.href = url;
  return {
    href: anchor.href,
    protocol: anchor.protocol,
    hostname: anchor.hostname,
    port: anchor.port,
    pathname: anchor.pathname,
    search: anchor.search,
    hash: anchor.hash,
    host: anchor.host,
    query: qs.parse(anchor.search),
  };
}

function scriptFetch(path, options = {}) {
  const { headers, method, body } = options;

  // Given any payload, Apps Script seems to coerce the method from 'get' to 'post'
  // (even if the payload is falsey).
  // Therefore, we need to ensure that the 'payload' key is only set if we really,
  // really mean to send a body.
  const payload = body ? { payload: body } : null;

  const requestOptions = {
    muteHttpExceptions: false,
    headers: headers || {},
    method: method || 'get',
    contentType: headers['Content-Type'] || headers['content-type'] || 'application/json',
    ...payload,
  };

  return new Promise((resolve, reject) => {
    google.script.run
      .withSuccessHandler(resolve)
      .withFailureHandler(reject)
      .makeURLFetchAppCall(path, requestOptions);
  })
    .then(response => {
      // In the actual fetch API, headers are case-insensitive (i.e., for methods like
      // headers.has and headers.get).
      // We need to approximate this case insensitivity, at least in functionality.
      const responseHeaders = Object.keys(response.headers).reduce((acc, header) => {
        const ret = acc;
        ret[header.toLowerCase()] = response.headers[header];
        return ret;
      }, {});
      const responseBody = response.body || null;

      return {
        headers: {
          has: header => !!responseHeaders[header.toLowerCase()],
          get: header => responseHeaders[header.toLowerCase()] || '',
        },
        text: () => Promise.resolve(responseBody),
        json: () => Promise.resolve(JSON.parse(responseBody)),
      };
    });
}

const WrappedFluxSdk = fluxSdkWrapper({
  parseUrl,
  WebSocket,
  base64Encode: btoa,
  base64Decode: atob,
  parseQuery: qs.parse,
  stringifyQuery: qs.stringify,
  fetch: scriptFetch,
});

function FluxSdk(clientId, options = {}) {
  return new WrappedFluxSdk(clientId, { ...options, implicit: true });
}

FluxSdk.handleCredentials = handleCredentials;

module.exports = FluxSdk;
<|endoftext|>


<commit_before>(function( window, document, undefined ) {
  'use strict';

  var PI2 = 2 * Math.PI;

  var running = false;

  var el = document.querySelector( '.container' );

  var tickFns = [];

  /**
   * TODO:
   * - Wires,
   * - Teleporter?
   * - Emitter.
   * - Trash?
   */

  tickFns.push((function drawHero() {
    var canvas = document.createElement( 'canvas' ),
        ctx    = canvas.getContext( '2d' );

    var size = 128;

    var width  = size,
        height = size;

    canvas.width  = width;
    canvas.height = height;
    canvas.style.backgroundColor = 'black';

    el.appendChild( canvas );

    var t = 0;

    function draw() {
      ctx.clearRect( 0, 0, ctx.canvas.width, ctx.canvas.height );

      ctx.save();
      ctx.translate( 0.5 * width, 0.5 * height );

      // Draw casing.
      ctx.lineWidth = 0.05 * width;

      // Top left.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, -Math.PI, -0.5 * Math.PI );
      ctx.strokeStyle = '#fef';
      ctx.stroke();

      // Top right.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, -0.5 * Math.PI, 0 );
      ctx.strokeStyle = '#cbe';
      ctx.stroke();

      // Bottom right.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, 0, 0.5 * Math.PI );
      ctx.strokeStyle = '#98b';
      ctx.stroke();

      // Bottom left.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, 0.5 * Math.PI, Math.PI );
      ctx.strokeStyle = '#658';
      ctx.stroke();

      // Draw main body.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.2 * width, 0, PI2 );
      ctx.fillStyle = '#ecf';
      ctx.fill();

      // Strokes.
      ctx.lineWidth = 0.02 * width;
      ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
      ctx.stroke();

      ctx.beginPath();
      ctx.arc( 0, 0, 0.18 * width, 0, PI2 );
      ctx.strokeStyle = '#fff';
      ctx.stroke();

      // Draw left eye.
      ctx.beginPath();
      ctx.rect( -0.1 * width, -0.06 * height, 0.04 * width, 0.08 * width );
      ctx.fillStyle = '#448';
      ctx.fill();

      // Draw right eye.
      ctx.beginPath();
      ctx.rect( 0.06 * width, -0.06 * height, 0.04 * width, 0.08 * width );
      ctx.fillStyle = '#448';
      ctx.fill();

      // Draw mouth.
      if ( t % 240 < 80 ) {
        ctx.beginPath();
        ctx.arc( 0, 0.02 * height, 0.1 * width, 0.25 * Math.PI, 0.75 * Math.PI );

        ctx.lineWidth = 0.03 * width;
        ctx.strokeStyle = '#448';
        ctx.stroke();
      } else if ( t % 240 < 160 ) {
        ctx.beginPath();
        ctx.rect( -0.02 * width, 0.08 * height, 0.04 * width, 0.04 * width );
        ctx.fillStyle = '#448';
        ctx.fill();
      } else {
        ctx.beginPath();
        ctx.rect( -0.06 * width, 0.08 * height, 0.12 * width, 0.02 * width );
        ctx.fillStyle = '#448';
        ctx.fill();
      }

      // Draw ring.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.35 * width, 0, PI2 );

      ctx.shadowColor = '#fff';
      ctx.shadowBlur = 0.05 * width + Math.random() * 0.05 * width;

      ctx.lineWidth = 0.07 * width;
      ctx.strokeStyle = '#fff';
      ctx.stroke();

      ctx.shadowBlur = 0;

      ctx.restore();
    }

    return function() {
      t++;
      draw();
    };
  }) ());

  tickFns.push((function drawEmitter() {
    var canvas = document.createElement( 'canvas' ),
        ctx    = canvas.getContext( '2d' );

    var size = 128;

    var width  = size,
        height = size;

    canvas.width  = width;
    canvas.height = height;
    canvas.style.backgroundColor = '#333';

    el.appendChild( canvas );

    var beamWidth  = 0.4 * width,
        beamHeight = 0.3 * height;

    var baseWidth  = 0.01 * beamWidth,
        baseHeight = beamHeight;

    function yRandom() {
      return Math.random() - 0.5;
    }

    var particles = [];
    var particleCount = 10;
    var radius;
    while ( particleCount-- ) {
      radius = ( 0.02 + Math.random() * 0.01 ) * width;
      particles.push({
        x: Math.random() * beamWidth + radius,
        y: yRandom() * ( beamHeight - radius ),
        radius: radius,
        vx: 1.8 * beamHeight,
        vy: 0
      });
    }

    function draw() {
      ctx.clearRect( 0, 0, width, height );

      ctx.save();
      ctx.translate( 0.5 * width, 0.5 * height );

      // Draw base.
      ctx.beginPath();
      ctx.rect( -0.5 * baseWidth, -0.5 * baseHeight, baseWidth, baseHeight );
      ctx.fillStyle = '#f55';
      ctx.fill();

      // Draw beam.
      ctx.beginPath();
      ctx.rect( 0, -0.5 * beamHeight, beamWidth, beamHeight );
      var grad = ctx.createLinearGradient( 0, 0, beamWidth, 0 );
      grad.addColorStop( 0, 'rgba(255, 128, 128, 0.8)' );
      grad.addColorStop( 1, 'rgba(255, 128, 128, 0.0)' );
      ctx.fillStyle = grad;
      ctx.fill();

      // Draw particles.
      particles.forEach(function( particle ) {
        var alpha = 1 - particle.x / beamWidth;

        ctx.beginPath();
        ctx.arc( particle.x, particle.y, particle.radius, 0, PI2 );
        ctx.fillStyle = 'rgba(255, 64, 64, ' + alpha + ')';
        ctx.fill();
      });

      ctx.restore();
    }

    return function( dt ) {
      particles.forEach(function( particle ) {
        particle.x += particle.vx * dt;
        particle.y += particle.vy * dt;

        if ( particle.x - particle.radius > beamWidth ) {
          particle.x = particle.radius;
          particle.y = yRandom() * ( beamHeight - radius );
        }
      });

      draw();
    };
  }) ());

  var prevTime = Date.now(),
      currTime;

  function tick() {
    currTime = Date.now();
    var dt = currTime - prevTime;
    prevTime = currTime;

    if ( dt > 1e2 ) {
      dt = 1e2;
    }

    dt *= 1e-3;

    tickFns.forEach(function( tickFn ) {
      tickFn( dt );
    });

    if ( !running ) {
      return;
    }

    window.requestAnimationFrame( tick );
  }

  tick();

  document.addEventListener( 'keydown', function( event ) {
    // ESC.
    if ( event.which === 27 ) {
      running = false;
    }

    // Space.
    if ( event.which === 32 ) {
      if ( !running ) {
        running = true;
        tick();
      }
    }
  });

  window.addEventListener( 'blur', function() {
    running = false;
  });
}) ( window, document );
<commit_msg>zoo: Color tweaks to emitter test. Increase portal prominence.<commit_after>(function( window, document, undefined ) {
  'use strict';

  var PI2 = 2 * Math.PI;

  var running = false;

  var el = document.querySelector( '.container' );

  var tickFns = [];

  /**
   * TODO:
   * - Wires,
   * - Teleporter?
   * - Emitter.
   * - Trash?
   */

  tickFns.push((function drawHero() {
    var canvas = document.createElement( 'canvas' ),
        ctx    = canvas.getContext( '2d' );

    var size = 128;

    var width  = size,
        height = size;

    canvas.width  = width;
    canvas.height = height;
    canvas.style.backgroundColor = 'black';

    el.appendChild( canvas );

    var t = 0;

    function draw() {
      ctx.clearRect( 0, 0, ctx.canvas.width, ctx.canvas.height );

      ctx.save();
      ctx.translate( 0.5 * width, 0.5 * height );

      // Draw casing.
      ctx.lineWidth = 0.05 * width;

      // Top left.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, -Math.PI, -0.5 * Math.PI );
      ctx.strokeStyle = '#fef';
      ctx.stroke();

      // Top right.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, -0.5 * Math.PI, 0 );
      ctx.strokeStyle = '#cbe';
      ctx.stroke();

      // Bottom right.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, 0, 0.5 * Math.PI );
      ctx.strokeStyle = '#98b';
      ctx.stroke();

      // Bottom left.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.22 * width, 0.5 * Math.PI, Math.PI );
      ctx.strokeStyle = '#658';
      ctx.stroke();

      // Draw main body.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.2 * width, 0, PI2 );
      ctx.fillStyle = '#ecf';
      ctx.fill();

      // Strokes.
      ctx.lineWidth = 0.02 * width;
      ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
      ctx.stroke();

      ctx.beginPath();
      ctx.arc( 0, 0, 0.18 * width, 0, PI2 );
      ctx.strokeStyle = '#fff';
      ctx.stroke();

      // Draw left eye.
      ctx.beginPath();
      ctx.rect( -0.1 * width, -0.06 * height, 0.04 * width, 0.08 * width );
      ctx.fillStyle = '#448';
      ctx.fill();

      // Draw right eye.
      ctx.beginPath();
      ctx.rect( 0.06 * width, -0.06 * height, 0.04 * width, 0.08 * width );
      ctx.fillStyle = '#448';
      ctx.fill();

      // Draw mouth.
      if ( t % 240 < 80 ) {
        ctx.beginPath();
        ctx.arc( 0, 0.02 * height, 0.1 * width, 0.25 * Math.PI, 0.75 * Math.PI );

        ctx.lineWidth = 0.03 * width;
        ctx.strokeStyle = '#448';
        ctx.stroke();
      } else if ( t % 240 < 160 ) {
        ctx.beginPath();
        ctx.rect( -0.02 * width, 0.08 * height, 0.04 * width, 0.04 * width );
        ctx.fillStyle = '#448';
        ctx.fill();
      } else {
        ctx.beginPath();
        ctx.rect( -0.06 * width, 0.08 * height, 0.12 * width, 0.02 * width );
        ctx.fillStyle = '#448';
        ctx.fill();
      }

      // Draw ring.
      ctx.beginPath();
      ctx.arc( 0, 0, 0.35 * width, 0, PI2 );

      ctx.shadowColor = '#fff';
      ctx.shadowBlur = 0.05 * width + Math.random() * 0.05 * width;

      ctx.lineWidth = 0.07 * width;
      ctx.strokeStyle = '#fff';
      ctx.stroke();

      ctx.shadowBlur = 0;

      ctx.restore();
    }

    return function() {
      t++;
      draw();
    };
  }) ());

  tickFns.push((function drawEmitter() {
    var canvas = document.createElement( 'canvas' ),
        ctx    = canvas.getContext( '2d' );

    var size = 128;

    var width  = size,
        height = size;

    canvas.width  = width;
    canvas.height = height;
    canvas.style.backgroundColor = '#333';

    el.appendChild( canvas );

    var beamWidth  = 0.4 * width,
        beamHeight = 0.3 * height;

    var baseWidth  = 0.1 * beamWidth,
        baseHeight = beamHeight;

    function yRandom() {
      return Math.random() - 0.5;
    }

    var t = 0;

    var particles = [];
    var particleCount = 10;
    var radius;
    while ( particleCount-- ) {
      radius = ( 0.02 + Math.random() * 0.01 ) * width;
      particles.push({
        x: Math.random() * beamWidth,
        y: yRandom() * ( beamHeight - radius ),
        radius: radius,
        vx: 1.8 * beamHeight,
        vy: 0
      });
    }

    var redPrefix  = 'rgba(255, 128, 128, ',
        bluePrefix = 'rgba(128, 128, 255, ';

    var colorPrefix = redPrefix;

    function draw() {
      if ( t % 120 < 60 ) {
        colorPrefix = redPrefix;
      } else {
        colorPrefix = bluePrefix;
      }

      ctx.clearRect( 0, 0, width, height );

      ctx.save();
      ctx.translate( 0.5 * width, 0.5 * height );

      // Draw beam.
      ctx.beginPath();
      ctx.rect( 0, -0.5 * beamHeight, beamWidth, beamHeight );
      var grad = ctx.createLinearGradient( 0, 0, beamWidth, 0 );
      grad.addColorStop( 0, colorPrefix + '0.8)' );
      grad.addColorStop( 1, colorPrefix + '0.0)' );
      ctx.fillStyle = grad;
      ctx.fill();

      // Draw particles.
      particles.forEach(function( particle ) {
        var alpha = 1 - particle.x / beamWidth;

        ctx.beginPath();
        ctx.arc( particle.x, particle.y, particle.radius, 0, PI2 );
        ctx.fillStyle = 'rgba(255, 255, 255, ' + alpha + ')';
        ctx.fill();
      });

      // Draw base.
      ctx.shadowBlur = 0.02 * width;
      ctx.shadowColor = colorPrefix + '1.0)';

      ctx.beginPath();
      ctx.moveTo( 0, -0.5 * baseHeight );
      ctx.lineTo( 0, 0.5 * baseHeight );

      ctx.lineCap = 'round';
      ctx.lineWidth = baseWidth;
      ctx.strokeStyle = '#fff';
      ctx.stroke();

      ctx.lineCap = 'butt';
      ctx.shadowBlur = 0;

      ctx.restore();
    }

    return function( dt ) {
      t++;

      particles.forEach(function( particle ) {
        particle.x += particle.vx * dt;
        particle.y += particle.vy * dt;

        if ( particle.x - particle.radius > beamWidth ) {
          particle.x = particle.radius;
          particle.y = yRandom() * ( beamHeight - radius );
        }
      });

      draw();
    };
  }) ());

  var prevTime = Date.now(),
      currTime;

  function tick() {
    currTime = Date.now();
    var dt = currTime - prevTime;
    prevTime = currTime;

    if ( dt > 1e2 ) {
      dt = 1e2;
    }

    dt *= 1e-3;

    tickFns.forEach(function( tickFn ) {
      tickFn( dt );
    });

    if ( !running ) {
      return;
    }

    window.requestAnimationFrame( tick );
  }

  tick();

  document.addEventListener( 'keydown', function( event ) {
    // ESC.
    if ( event.which === 27 ) {
      running = false;
    }

    // Space.
    if ( event.which === 32 ) {
      if ( !running ) {
        running = true;
        tick();
      }
    }
  });

  window.addEventListener( 'blur', function() {
    running = false;
  });
}) ( window, document );
<|endoftext|>


<commit_before>/*eslint no-console:0 */
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
var address = 'localhost';
// var open = require('open');

//new WebpackDevServer(webpack(config), config.devServer)
//.listen(config.port, 'localhost', function(err) {
//  if (err) {
//    console.log(err);
//  }
//  console.log('Listening at localhost:' + config.port);
//  //console.log('Opening your system browser...');
//  //open('http://localhost:' + config.port);
//});

new WebpackDevServer(webpack(config), { https: true, hot: true })
//.listen(config.port, config.devServer.listenHost, function (err) {
.listen(8080, address, function (err) {
    if (err) {
        console.log(err);
    }
    console.log('Listening at '+ address);
    //console.log('Opening your system browser...');
    //open('http://localhost:' + config.port);
});
<commit_msg>Adjusted the webpack devserver to always serve index.html<commit_after>var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
var address = 'localhost';

var server = new WebpackDevServer(webpack(config),
  {
    https: true,
    hot: true
  });

// Important part. Send down index.html for all requests
server.use('/', function(req, res) {
  res.sendFile(path.join(__dirname+'/index.html'));
});

server.listen(8080, address, function (err) {
    if (err) {
        console.log(err);
    }
    console.log('Listening at '+ address);
});
<|endoftext|>


<commit_before>(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.hull = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
function _cross(o, a, b) {
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
}

function _upperTangent(pointset) {
    var lower = [];
    for (var l = 0; l < pointset.length; l++) {
        while (lower.length >= 2 && (_cross(lower[lower.length - 2], lower[lower.length - 1], pointset[l]) <= 0)) {
            lower.pop();
        }
        lower.push(pointset[l]);
    }
    lower.pop();
    return lower;
}

function _lowerTangent(pointset) {
    var reversed = pointset.reverse(),
        upper = [];
    for (var u = 0; u < reversed.length; u++) {
        while (upper.length >= 2 && (_cross(upper[upper.length - 2], upper[upper.length - 1], reversed[u]) <= 0)) {
            upper.pop();
        }
        upper.push(reversed[u]);
    }
    upper.pop();
    return upper;
}

// pointset has to be sorted by X
function convex(pointset) {
    var convex,
        upper = _upperTangent(pointset),
        lower = _lowerTangent(pointset);
    convex = lower.concat(upper);
    convex.push(pointset[0]);
    return convex;
}

module.exports = convex;

},{}],2:[function(require,module,exports){
module.exports = {

    toXy: function(pointset, format) {
        if (format === undefined) {
            return pointset.slice();
        }
        return pointset.map(function(pt) {
            /*jslint evil: true */
            var _getXY = new Function('pt', 'return [pt' + format[0] + ',' + 'pt' + format[1] + '];');
            return _getXY(pt);
        });
    },

    fromXy: function(pointset, format) {
        if (format === undefined) {
            return pointset.slice();
        }
        return pointset.map(function(pt) {
            /*jslint evil: true */
            var _getObj = new Function('pt', 'var o = {}; o' + format[0] + '= pt[0]; o' + format[1] + '= pt[1]; return o;');
            return _getObj(pt);
        });
    }

}
},{}],3:[function(require,module,exports){
function Grid(points, cellSize) {
    this._cells = [];
    this._cellSize = cellSize;

    points.forEach(function(point) {
        var cellXY = this.point2CellXY(point),
            x = cellXY[0],
            y = cellXY[1];
        if (this._cells[x] === undefined) {
            this._cells[x] = [];
        }
        if (this._cells[x][y] === undefined) {
            this._cells[x][y] = [];
        }
        this._cells[x][y].push(point);
    }, this);
}

Grid.prototype = {
    cellPoints: function(x, y) { // (Number, Number) -> Array
        return (this._cells[x] !== undefined && this._cells[x][y] !== undefined) ? this._cells[x][y] : [];
    },

    rangePoints: function(bbox) { // (Array) -> Array
        var tlCellXY = this.point2CellXY([bbox[0], bbox[1]]),
            brCellXY = this.point2CellXY([bbox[2], bbox[3]]),
            points = [];

        for (var x = tlCellXY[0]; x <= brCellXY[0]; x++) {
            for (var y = tlCellXY[1]; y <= brCellXY[1]; y++) {
                points = points.concat(this.cellPoints(x, y));
            }
        }

        return points;
    },

    removePoint: function(point) { // (Array) -> Array
        var cellXY = this.point2CellXY(point),
            cell = this._cells[cellXY[0]][cellXY[1]],
            pointIdxInCell;

        for (var i = 0; i < cell.length; i++) {
            if (cell[i][0] === point[0] && cell[i][1] === point[1]) {
                pointIdxInCell = i;
                break;
            }
        }

        cell.splice(pointIdxInCell, 1);

        return cell;
    },

    point2CellXY: function(point) { // (Array) -> Array
        var x = (point[0] / this._cellSize) | 0,
            y = (point[1] / this._cellSize) | 0;
        return [x, y];
    },

    extendBbox: function(bbox, scaleFactor) { // (Array, Number) -> Array
        return [
            bbox[0] - (scaleFactor * this._cellSize),
            bbox[1] - (scaleFactor * this._cellSize),
            bbox[2] + (scaleFactor * this._cellSize),
            bbox[3] + (scaleFactor * this._cellSize)
        ];
    }
};

function grid(points, cellSize) {
    return new Grid(points, cellSize);
}

module.exports = grid;
},{}],4:[function(require,module,exports){
/*
 (c) 2014-2016, Andrii Heonia
 Hull.js, a JavaScript library for concave hull generation by set of points.
 https://github.com/AndriiHeonia/hull
*/

'use strict';

var intersect = require('./intersect.js');
var grid = require('./grid.js');
var formatUtil = require('./format.js');
var convexHull = require('./convex.js');

function _filterDuplicates(pointset) {
    return pointset.filter(function(el, idx, arr) {
        var prevEl = arr[idx - 1];
        return idx === 0 || !(prevEl[0] === el[0] && prevEl[1] === el[1]);
    });
}

function _sortByX(pointset) {
    return pointset.sort(function(a, b) {
        if (a[0] == b[0]) {
            return a[1] - b[1];
        } else {
            return a[0] - b[0];
        }
    });
}

function _sqLength(a, b) {
    return Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2);
}

function _cos(o, a, b) {
    var aShifted = [a[0] - o[0], a[1] - o[1]],
        bShifted = [b[0] - o[0], b[1] - o[1]],
        sqALen = _sqLength(o, a),
        sqBLen = _sqLength(o, b),
        dot = aShifted[0] * bShifted[0] + aShifted[1] * bShifted[1];

    return dot / Math.sqrt(sqALen * sqBLen);
}

function _intersect(segment, pointset) {
    for (var i = 0; i < pointset.length - 1; i++) {
        var seg = [pointset[i], pointset[i + 1]];
        if (segment[0][0] === seg[0][0] && segment[0][1] === seg[0][1] ||
            segment[0][0] === seg[1][0] && segment[0][1] === seg[1][1]) {
            continue;
        }
        if (intersect(segment, seg)) {
            return true;
        }
    }
    return false;
}

function _occupiedArea(pointset) {
    var minX = Infinity,
        minY = Infinity,
        maxX = -Infinity,
        maxY = -Infinity;

    for (var i = pointset.length - 1; i >= 0; i--) {
        if (pointset[i][0] < minX) {
            minX = pointset[i][0];
        }
        if (pointset[i][1] < minY) {
            minY = pointset[i][1];
        }
        if (pointset[i][0] > maxX) {
            maxX = pointset[i][0];
        }
        if (pointset[i][1] > maxY) {
            maxY = pointset[i][1];
        }
    }

    return [
        maxX - minX, // width
        maxY - minY  // height
    ];
}

function _bBoxAround(edge) {
    return [
        Math.min(edge[0][0], edge[1][0]), // left
        Math.min(edge[0][1], edge[1][1]), // top
        Math.max(edge[0][0], edge[1][0]), // right
        Math.max(edge[0][1], edge[1][1])  // bottom
    ];
}

function _midPoint(edge, innerPoints, convex) {
    var point = null,
        angle1Cos = MAX_CONCAVE_ANGLE_COS,
        angle2Cos = MAX_CONCAVE_ANGLE_COS,
        a1Cos, a2Cos;

    for (var i = 0; i < innerPoints.length; i++) {
        a1Cos = _cos(edge[0], edge[1], innerPoints[i]);
        a2Cos = _cos(edge[1], edge[0], innerPoints[i]);

        if (a1Cos > angle1Cos && a2Cos > angle2Cos &&
            !_intersect([edge[0], innerPoints[i]], convex) &&
            !_intersect([edge[1], innerPoints[i]], convex)) {

            angle1Cos = a1Cos;
            angle2Cos = a2Cos;
            point = innerPoints[i];
        }
    }

    return point;
}

function _concave(convex, maxSqEdgeLen, maxSearchArea, grid, edgeSkipList) {
    var edge,
        keyInSkipList,
        scaleFactor,
        midPoint,
        bBoxAround,
        bBoxWidth,
        bBoxHeight,
        midPointInserted = false;

    for (var i = 0; i < convex.length - 1; i++) {
        edge = [convex[i], convex[i + 1]];
        // generate a key in the format X0,Y0,X1,Y1
        keyInSkipList = edge[0][0] + ',' + edge[0][1] + ',' + edge[1][0] + ',' + edge[1][1];

        if (_sqLength(edge[0], edge[1]) < maxSqEdgeLen ||
            edgeSkipList.has(keyInSkipList)) { continue; }

        scaleFactor = 0;
        bBoxAround = _bBoxAround(edge);
        do {
            bBoxAround = grid.extendBbox(bBoxAround, scaleFactor);
            bBoxWidth = bBoxAround[2] - bBoxAround[0];
            bBoxHeight = bBoxAround[3] - bBoxAround[1];

            midPoint = _midPoint(edge, grid.rangePoints(bBoxAround), convex);
            scaleFactor++;
        }  while (midPoint === null && (maxSearchArea[0] > bBoxWidth || maxSearchArea[1] > bBoxHeight));

        if (bBoxWidth >= maxSearchArea[0] && bBoxHeight >= maxSearchArea[1]) {
            edgeSkipList.add(keyInSkipList);
        }

        if (midPoint !== null) {
            convex.splice(i + 1, 0, midPoint);
            grid.removePoint(midPoint);
            midPointInserted = true;
        }
    }

    if (midPointInserted) {
        return _concave(convex, maxSqEdgeLen, maxSearchArea, grid, edgeSkipList);
    }

    return convex;
}

function hull(pointset, concavity, format) {
    var convex,
        concave,
        innerPoints,
        occupiedArea,
        maxSearchArea,
        cellSize,
        points,
        maxEdgeLen = concavity || 20;

    points = _filterDuplicates(_sortByX(formatUtil.toXy(pointset, format)));

    if (points.length < 4) {
        return points.concat([points[0]]);
    }

    occupiedArea = _occupiedArea(points);
    maxSearchArea = [
        occupiedArea[0] * MAX_SEARCH_BBOX_SIZE_PERCENT,
        occupiedArea[1] * MAX_SEARCH_BBOX_SIZE_PERCENT
    ];

    convex = convexHull(points);
    innerPoints = points.filter(function(pt) {
        return convex.indexOf(pt) < 0;
    });

    cellSize = Math.ceil(1 / (points.length / (occupiedArea[0] * occupiedArea[1])));

    concave = _concave(
        convex, Math.pow(maxEdgeLen, 2),
        maxSearchArea, grid(innerPoints, cellSize), new Set());

    if (format) {
        return formatUtil.fromXy(concave, format);
    } else {
        return concave;
    }
}

var MAX_CONCAVE_ANGLE_COS = Math.cos(90 / (180 / Math.PI)); // angle = 90 deg
var MAX_SEARCH_BBOX_SIZE_PERCENT = 0.6;

module.exports = hull;

},{"./convex.js":1,"./format.js":2,"./grid.js":3,"./intersect.js":5}],5:[function(require,module,exports){
function ccw(x1, y1, x2, y2, x3, y3) {
    var cw = ((y3 - y1) * (x2 - x1)) - ((y2 - y1) * (x3 - x1));
    return cw > 0 ? true : cw < 0 ? false : true; // colinear
}

function intersect(seg1, seg2) {
  var x1 = seg1[0][0], y1 = seg1[0][1],
      x2 = seg1[1][0], y2 = seg1[1][1],
      x3 = seg2[0][0], y3 = seg2[0][1],
      x4 = seg2[1][0], y4 = seg2[1][1];

    return ccw(x1, y1, x3, y3, x4, y4) !== ccw(x2, y2, x3, y3, x4, y4) && ccw(x1, y1, x2, y2, x3, y3) !== ccw(x1, y1, x2, y2, x4, y4);
}

module.exports = intersect;
},{}]},{},[4])(4)
});
<commit_msg>Build<commit_after>(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.hull = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
function _cross(o, a, b) {
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
}

function _upperTangent(pointset) {
    var lower = [];
    for (var l = 0; l < pointset.length; l++) {
        while (lower.length >= 2 && (_cross(lower[lower.length - 2], lower[lower.length - 1], pointset[l]) <= 0)) {
            lower.pop();
        }
        lower.push(pointset[l]);
    }
    lower.pop();
    return lower;
}

function _lowerTangent(pointset) {
    var reversed = pointset.reverse(),
        upper = [];
    for (var u = 0; u < reversed.length; u++) {
        while (upper.length >= 2 && (_cross(upper[upper.length - 2], upper[upper.length - 1], reversed[u]) <= 0)) {
            upper.pop();
        }
        upper.push(reversed[u]);
    }
    upper.pop();
    return upper;
}

// pointset has to be sorted by X
function convex(pointset) {
    var convex,
        upper = _upperTangent(pointset),
        lower = _lowerTangent(pointset);
    convex = lower.concat(upper);
    convex.push(pointset[0]);
    return convex;
}

module.exports = convex;

},{}],2:[function(require,module,exports){
module.exports = {

    toXy: function(pointset, format) {
        if (format === undefined) {
            return pointset.slice();
        }
        return pointset.map(function(pt) {
            /*jslint evil: true */
            var _getXY = new Function('pt', 'return [pt' + format[0] + ',' + 'pt' + format[1] + '];');
            return _getXY(pt);
        });
    },

    fromXy: function(pointset, format) {
        if (format === undefined) {
            return pointset.slice();
        }
        return pointset.map(function(pt) {
            /*jslint evil: true */
            var _getObj = new Function('pt', 'var o = {}; o' + format[0] + '= pt[0]; o' + format[1] + '= pt[1]; return o;');
            return _getObj(pt);
        });
    }

}
},{}],3:[function(require,module,exports){
function Grid(points, cellSize) {
    this._cells = [];
    this._cellSize = cellSize;
    this._reverseCellSize = 1 / cellSize;

    for (let i = 0; i < points.length; i++) {
        const point = points[i];
        const x = this.point2Cell(point[0]);
        const y = this.point2Cell(point[1]);
        if (this._cells[x] === undefined) {
            const array = [];
            array[y] = [point];
            this._cells[x] = array;
        } else if (this._cells[x][y] === undefined) {
            this._cells[x][y] = [point];
        } else {
            this._cells[x][y].push(point);
        }
    }
}

Grid.prototype = {
    cellPoints: function(x, y) { // (Number, Number) -> Array
        return (this._cells[x] !== undefined && this._cells[x][y] !== undefined) ? this._cells[x][y] : [];
    },

    rangePoints: function(bbox) { // (Array) -> Array
        const tlCellX = this.point2Cell(bbox[0]);
        const tlCellY = this.point2Cell(bbox[1]);
        const brCellX = this.point2Cell(bbox[2]);
        const brCellY = this.point2Cell(bbox[3]);
        const points = [];

        for (var x = tlCellX; x <= brCellX; x++) {
            for (var y = tlCellY; y <= brCellY; y++) {
                Array.prototype.push.apply(points, this.cellPoints(x, y));
            }
        }

        return points;
    },

    removePoint: function(point) { // (Array) -> Array
        const cellX = this.point2Cell(point[0]);
        const cellY = this.point2Cell(point[1]);
        const cell = this._cells[cellX][cellY];
        let pointIdxInCell;

        for (var i = 0; i < cell.length; i++) {
            if (cell[i][0] === point[0] && cell[i][1] === point[1]) {
                pointIdxInCell = i;
                break;
            }
        }

        cell.splice(pointIdxInCell, 1);

        return cell;
    },

    point2Cell: function(x) { // (Array) -> Array
        return (x * this._reverseCellSize) | 0;
    },

    extendBbox: function(bbox, scaleFactor) { // (Array, Number) -> Array
        return [
            bbox[0] - (scaleFactor * this._cellSize),
            bbox[1] - (scaleFactor * this._cellSize),
            bbox[2] + (scaleFactor * this._cellSize),
            bbox[3] + (scaleFactor * this._cellSize)
        ];
    }
};

function grid(points, cellSize) {
    return new Grid(points, cellSize);
}

module.exports = grid;
},{}],4:[function(require,module,exports){
/*
 (c) 2014-2016, Andrii Heonia
 Hull.js, a JavaScript library for concave hull generation by set of points.
 https://github.com/AndriiHeonia/hull
*/

'use strict';

var intersect = require('./intersect.js');
var grid = require('./grid.js');
var formatUtil = require('./format.js');
var convexHull = require('./convex.js');

function _filterDuplicates(pointset) {
    const unique = [pointset[0]];
    let lastPoint = pointset[0];
    for (let i = 1; i < pointset.length; i++) {
        const currentPoint = pointset[i];
        if (lastPoint[0] !== currentPoint[0] || lastPoint[1] !== currentPoint[1]) {
            unique.push(currentPoint);
        }
        lastPoint = currentPoint;
    }
    return unique;
}

function _sortByX(pointset) {
    return pointset.sort(function(a, b) {
        return (a[0] - b[0]) || (a[1] - b[1]);
    });
}

function _sqLength(a, b) {
    return Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2);
}

function _cos(o, a, b) {
    var aShifted = [a[0] - o[0], a[1] - o[1]],
        bShifted = [b[0] - o[0], b[1] - o[1]],
        sqALen = _sqLength(o, a),
        sqBLen = _sqLength(o, b),
        dot = aShifted[0] * bShifted[0] + aShifted[1] * bShifted[1];

    return dot / Math.sqrt(sqALen * sqBLen);
}

function _intersect(segment, pointset) {
    for (var i = 0; i < pointset.length - 1; i++) {
        var seg = [pointset[i], pointset[i + 1]];
        if (segment[0][0] === seg[0][0] && segment[0][1] === seg[0][1] ||
            segment[0][0] === seg[1][0] && segment[0][1] === seg[1][1]) {
            continue;
        }
        if (intersect(segment, seg)) {
            return true;
        }
    }
    return false;
}

function _occupiedArea(pointset) {
    var minX = Infinity,
        minY = Infinity,
        maxX = -Infinity,
        maxY = -Infinity;

    for (var i = pointset.length - 1; i >= 0; i--) {
        if (pointset[i][0] < minX) {
            minX = pointset[i][0];
        }
        if (pointset[i][1] < minY) {
            minY = pointset[i][1];
        }
        if (pointset[i][0] > maxX) {
            maxX = pointset[i][0];
        }
        if (pointset[i][1] > maxY) {
            maxY = pointset[i][1];
        }
    }

    return [
        maxX - minX, // width
        maxY - minY  // height
    ];
}

function _bBoxAround(edge) {
    return [
        Math.min(edge[0][0], edge[1][0]), // left
        Math.min(edge[0][1], edge[1][1]), // top
        Math.max(edge[0][0], edge[1][0]), // right
        Math.max(edge[0][1], edge[1][1])  // bottom
    ];
}

function _midPoint(edge, innerPoints, convex) {
    var point = null,
        angle1Cos = MAX_CONCAVE_ANGLE_COS,
        angle2Cos = MAX_CONCAVE_ANGLE_COS,
        a1Cos, a2Cos;

    for (var i = 0; i < innerPoints.length; i++) {
        a1Cos = _cos(edge[0], edge[1], innerPoints[i]);
        a2Cos = _cos(edge[1], edge[0], innerPoints[i]);

        if (a1Cos > angle1Cos && a2Cos > angle2Cos &&
            !_intersect([edge[0], innerPoints[i]], convex) &&
            !_intersect([edge[1], innerPoints[i]], convex)) {

            angle1Cos = a1Cos;
            angle2Cos = a2Cos;
            point = innerPoints[i];
        }
    }

    return point;
}

function _concave(convex, maxSqEdgeLen, maxSearchArea, grid, edgeSkipList) {
    var edge,
        keyInSkipList,
        scaleFactor,
        midPoint,
        bBoxAround,
        bBoxWidth,
        bBoxHeight,
        midPointInserted = false;

    for (var i = 0; i < convex.length - 1; i++) {
        edge = [convex[i], convex[i + 1]];
        // generate a key in the format X0,Y0,X1,Y1
        keyInSkipList = edge[0][0] + ',' + edge[0][1] + ',' + edge[1][0] + ',' + edge[1][1];

        if (_sqLength(edge[0], edge[1]) < maxSqEdgeLen ||
            edgeSkipList.has(keyInSkipList)) { continue; }

        scaleFactor = 0;
        bBoxAround = _bBoxAround(edge);
        do {
            bBoxAround = grid.extendBbox(bBoxAround, scaleFactor);
            bBoxWidth = bBoxAround[2] - bBoxAround[0];
            bBoxHeight = bBoxAround[3] - bBoxAround[1];

            midPoint = _midPoint(edge, grid.rangePoints(bBoxAround), convex);
            scaleFactor++;
        }  while (midPoint === null && (maxSearchArea[0] > bBoxWidth || maxSearchArea[1] > bBoxHeight));

        if (bBoxWidth >= maxSearchArea[0] && bBoxHeight >= maxSearchArea[1]) {
            edgeSkipList.add(keyInSkipList);
        }

        if (midPoint !== null) {
            convex.splice(i + 1, 0, midPoint);
            grid.removePoint(midPoint);
            midPointInserted = true;
        }
    }

    if (midPointInserted) {
        return _concave(convex, maxSqEdgeLen, maxSearchArea, grid, edgeSkipList);
    }

    return convex;
}

function hull(pointset, concavity, format) {
    var convex,
        concave,
        innerPoints,
        occupiedArea,
        maxSearchArea,
        cellSize,
        points,
        maxEdgeLen = concavity || 20;

    points = _filterDuplicates(_sortByX(formatUtil.toXy(pointset, format)));

    if (points.length < 4) {
        return points.concat([points[0]]);
    }

    occupiedArea = _occupiedArea(points);
    maxSearchArea = [
        occupiedArea[0] * MAX_SEARCH_BBOX_SIZE_PERCENT,
        occupiedArea[1] * MAX_SEARCH_BBOX_SIZE_PERCENT
    ];

    convex = convexHull(points);
    innerPoints = points.filter(function(pt) {
        return convex.indexOf(pt) < 0;
    });

    cellSize = Math.ceil(1 / (points.length / (occupiedArea[0] * occupiedArea[1])));

    concave = _concave(
        convex, Math.pow(maxEdgeLen, 2),
        maxSearchArea, grid(innerPoints, cellSize), new Set());

    if (format) {
        return formatUtil.fromXy(concave, format);
    } else {
        return concave;
    }
}

var MAX_CONCAVE_ANGLE_COS = Math.cos(90 / (180 / Math.PI)); // angle = 90 deg
var MAX_SEARCH_BBOX_SIZE_PERCENT = 0.6;

module.exports = hull;

},{"./convex.js":1,"./format.js":2,"./grid.js":3,"./intersect.js":5}],5:[function(require,module,exports){
function ccw(x1, y1, x2, y2, x3, y3) {
    var cw = ((y3 - y1) * (x2 - x1)) - ((y2 - y1) * (x3 - x1));
    return cw > 0 ? true : cw < 0 ? false : true; // colinear
}

function intersect(seg1, seg2) {
  var x1 = seg1[0][0], y1 = seg1[0][1],
      x2 = seg1[1][0], y2 = seg1[1][1],
      x3 = seg2[0][0], y3 = seg2[0][1],
      x4 = seg2[1][0], y4 = seg2[1][1];

    return ccw(x1, y1, x3, y3, x4, y4) !== ccw(x2, y2, x3, y3, x4, y4) && ccw(x1, y1, x2, y2, x3, y3) !== ccw(x1, y1, x2, y2, x4, y4);
}

module.exports = intersect;
},{}]},{},[4])(4)
});
<|endoftext|>


<commit_before>(function(jQuery) {

var $ = jQuery;

if (!window.Echo) window.Echo = {};

if (!Echo.GUI) Echo.GUI = {};

if (Echo.GUI.Modal) return;

/**
 * @class Echo.GUI.Modal
 * Class wrapper for bootstrap-modal.js.
 * The Echo.GUI.Modal class provides a simplified interface to work with the
 * Bootstrap Modal JS class.
 * Echo wrapper assembles the HTML code required for Bootstrap Modal JS class
 * based on the parameters specified in the config and initializes
 * the corresponding Bootstrap JS class.
 *
 * Example:
 * 	var myModal = new Echo.GUI.Modal({
 * 		"show": true,
 * 		"backdrop": true,
 * 		 "keyboard": true,
 * 		 "closeButton": true,
 * 		 "remote": false,
 * 		 "extraClass": "",
 * 		 "data": {
 * 			 "title": "Modal title",
 * 			 "body": "Modal body",
 * 			 "buttons": [{
 * 				 "title": "Button1",
 * 				 "extraClass": "echo-button1-class",
 * 				 "handler": function() {}
 * 			 }, {
 * 				 "title": "Button2",
 * 				 "extraClass": "echo-button2-class",
 * 				 "handler": function() {}
 * 			 }]
 * 		 },
 * 		 "width": "400",
 * 		 "height": "500",
 * 		 "padding": "10",
 * 		 "footer": true,
 * 		 "header": true,
 * 		 "fade": true
 * 	});
 *
 * @constructor
 * Creates a new modal dialog. The dialog can be created in visible or hidden (default) modes. In order to initialize the modal dialog instance in visible state, the "show" parameter should be defined as 'true' (JS boolean) during the constructor call. For the modal dialogs hidden by default the "show" function can be applied to reveal it when appropriate.
 *
 * @param {Object} config
 * Modal configuration.
 *
 * @cfg {Boolean} [show=false]
 * Defines whether the modal dialog should be displayed right after it is created.
 *
 * @cfg {Boolean} [backdrop=true]
 * Defines whether the semi-transparent backdrop underneath the modal dialog box should be displayed.
 *
 * @cfg {Boolean} [keyboard=true]
 * Defines whether modal dialog should be closed if the "Esc"(escape) key is pressed on the keyboard.
 *
 * @cfg {Boolean} [closeButton=true]
 * Defines whether the close ("X") icon in the top right corner of the dialog box should be shown.
 *
 * @cfg {String} [remote=false]
 * Remote URL.
 * If a remote URL is provided, content will be loaded via jQuery load method and injected into the modal dialog body.
 *
 * @cfg {String} [extraClass=""]
 * Custom class name which should be added to the modal dialog container.
 *
 * @cfg {Object} data
 *
 * @cfg {String|Function} data.title
 * Modal dialog title (can contain HTML tags).
 * If function passed in this parameter, the function result will be used.
 *
 * @cfg {String|Function} data.body
 * The main content of the dialog (can contain HTML tags).
 * If function passed in this parameter, the function result will be used.
 *
 * @cfg {Array} data.buttons
 * You can specify the custom buttons in this parameter which should be displayed in the modal footer.
 * Each array element is the object with the following parameters:
 * 	title      - button title.
 * 	extraClass - custom class name which will be added to the button.
 * 	handler    - function which will be called when button is clicked.
 *
 * @cfg {Number} [width=null]
 * Modal dialog width.
 *
 * @cfg {Number} [height=null]
 * Modal dialog height.
 *
 * @cfg {Number} [padding=null]
 * Modal dialog padding.
 *
 * @cfg {Boolean} [footer=true]
 * Display modal dialog footer.
 * Should be true if you want to display custom buttons.
 *
 * @cfg {Boolean} [header=true]
 * Display modal header.
 *
 * @cfg {Boolean} [fade=false]
 * Apply a CSS fade transition.
 */
Echo.GUI.Modal = Echo.Utils.inherit(Echo.GUI, function(config) {
	Echo.GUI.call(this, config, {
		"targetless": true,
		"show": false,
		"backdrop": true,
		"keyboard": true,
		"closeButton": true,
		"remote": false,
		"extraClass": "",
		"data": {},
		"width": null,
		"height": null,
		"padding": null,
		"footer": true,
		"header": true,
		"onHide": function() {},
		"onShow": function() {},
		"fade": false
	});
});

Echo.GUI.Modal.prototype.refresh = function() {
	var self = this;
	if (this.rendered) this.destroy();

	this.rendered = true;
	var css = this.config.get("fade") ? "fade" : "hide";
	this.element = this.element || $('
');
	this.element.empty();

	this._assembleHeader();
	this._assembleBody();
	this._assembleFooter();
	this._assembleBackdrop();

	this.element.on("hidden", function() {
		self.destroy();
	});
	this.element.on("shown", function() {
		self.config.get("onShow").call(self, self.element);
	});

	this.config.get("fade") && $.support.transition && this.element.one($.support.transition.end, function() {
		self.element.focus().trigger("shown");
	});

	this.element.appendTo("body")
		.wrap("
")
		.modal(this.config.getAsHash());

	// if we try show few modals in one time, then event "focusin.modal" be called infinity times
	$(document).off('focusin.modal');
};

/**
 * Shows the modal dialog.
 * 	myModal.show();
 */
Echo.GUI.Modal.prototype.show = function() {
	this.element.modal("show");
};

/**
 * Hides the modal dialog and removes the dialog instance.
 *  myModal.destroy();
 */
Echo.GUI.Modal.prototype.destroy = function() {
	if (this.rendered) {
		this.element.modal("hide");
		this.rendered = false;
		this.backdrop && this.backdrop.remove();
		this.element.find('iframe').hide().attr('src', '//about:blank').end().empty();
		this.element.unwrap().remove();
		this.config.get("onHide").call(this, this.element);
	}
};

/**
 * Hides the modal dialog.
 * 	myModal.hide();
 */
Echo.GUI.Modal.prototype.hide = function() {
	if (this.rendered) {
		this.element.modal("hide");
	}
};

Echo.GUI.Modal.prototype._assembleHeader = function() {
	if (this.config.get("header")) {
		var header = this._addSection("modal-header", $("
").append(this.config.get("data").title));
		if (this.config.get("closeButton")) {
			$('<button aria-hidden="true" data-dismiss="modal" class="close" type="button">')
				.append("×").prependTo(header);
		}
	}
}

Echo.GUI.Modal.prototype._assembleBody = function() {
	var self = this;
	var body = this._addSection("modal-body", this.config.get("data").body);
	if (this.config.get("href")) {
		this.element.one("shown", function() {
			body.append('');
		});
	}
	if (this.config.get("width") !== null) {
		this.element.width(this.config.get("width"))
			.css({"margin-left": this.config.get("width") * -0.5});
	}
	if (this.config.get("height") !== null) {
		body.height(this.config.get("height"));
	}
	if (this.config.get("padding") !== null) {
		body.css("padding", this.config.get("padding"));
	}
	if (this.config.get("extraClass")) {
		this.element.addClass(this.config.get("extraClass"));
	}
}

Echo.GUI.Modal.prototype._assembleFooter = function() {
	var self = this;
	if (this.config.get("footer")) {
		var footer = this._addSection("modal-footer");
		if (this.config.get("data").buttons) {
			$.map(this.config.get("data").buttons, function(button) {
				var el = $("<button>").addClass("btn").append(button.title);
				if (button.extraClass) {
					el.addClass(button.extraClass);
				}
				el.click(function() {
					button.handler && button.handler.call(self, this);
				});
				el.appendTo(footer);
			});
		}
	}
}

Echo.GUI.Modal.prototype._assembleBackdrop = function() {
	var self = this;
	if (self.config.get("backdrop")) {
		if (self.config.get("fade")) {
			self.element.on("show", function() {
				var modal = self.element.data('modal');
				var shown = modal.isShown;
				var backdrop = modal.options.backdrop;

				modal.options.backdrop = true;
				modal.isShown = true;
				modal.backdrop(function(){});
				modal.isShown = shown;
				modal.options.backdrop = backdrop;

				self.element.addClass("in");

				self.backdrop = modal.$backdrop.wrap("
").parent();
			});
		} else {
			self.element.on("shown", function() {
				self.backdrop = self.element.data('modal').$backdrop.wrap("
").parent();
			});
		}
		// we used manual control for backdrop if fade is true
		if (self.config.get("fade")) {
			self.config.set("backdrop", false);
		}
	}
}

Echo.GUI.Modal.prototype._addSection = function(css, content) {
	var section = $('
');
	this.element.append(section);
	if (content) {
		section.append($.isFunction(content) ? content(section) : content);
	}
	return section;
};

})(Echo.jQuery);
<commit_msg>single quotes <=> double quotes<commit_after>(function(jQuery) {

var $ = jQuery;

if (!window.Echo) window.Echo = {};

if (!Echo.GUI) Echo.GUI = {};

if (Echo.GUI.Modal) return;

/**
 * @class Echo.GUI.Modal
 * Class wrapper for bootstrap-modal.js.
 * The Echo.GUI.Modal class provides a simplified interface to work with the
 * Bootstrap Modal JS class.
 * Echo wrapper assembles the HTML code required for Bootstrap Modal JS class
 * based on the parameters specified in the config and initializes
 * the corresponding Bootstrap JS class.
 *
 * Example:
 * 	var myModal = new Echo.GUI.Modal({
 * 		"show": true,
 * 		"backdrop": true,
 * 		 "keyboard": true,
 * 		 "closeButton": true,
 * 		 "remote": false,
 * 		 "extraClass": "",
 * 		 "data": {
 * 			 "title": "Modal title",
 * 			 "body": "Modal body",
 * 			 "buttons": [{
 * 				 "title": "Button1",
 * 				 "extraClass": "echo-button1-class",
 * 				 "handler": function() {}
 * 			 }, {
 * 				 "title": "Button2",
 * 				 "extraClass": "echo-button2-class",
 * 				 "handler": function() {}
 * 			 }]
 * 		 },
 * 		 "width": "400",
 * 		 "height": "500",
 * 		 "padding": "10",
 * 		 "footer": true,
 * 		 "header": true,
 * 		 "fade": true
 * 	});
 *
 * @constructor
 * Creates a new modal dialog. The dialog can be created in visible or hidden (default) modes. In order to initialize the modal dialog instance in visible state, the "show" parameter should be defined as 'true' (JS boolean) during the constructor call. For the modal dialogs hidden by default the "show" function can be applied to reveal it when appropriate.
 *
 * @param {Object} config
 * Modal configuration.
 *
 * @cfg {Boolean} [show=false]
 * Defines whether the modal dialog should be displayed right after it is created.
 *
 * @cfg {Boolean} [backdrop=true]
 * Defines whether the semi-transparent backdrop underneath the modal dialog box should be displayed.
 *
 * @cfg {Boolean} [keyboard=true]
 * Defines whether modal dialog should be closed if the "Esc"(escape) key is pressed on the keyboard.
 *
 * @cfg {Boolean} [closeButton=true]
 * Defines whether the close ("X") icon in the top right corner of the dialog box should be shown.
 *
 * @cfg {String} [remote=false]
 * Remote URL.
 * If a remote URL is provided, content will be loaded via jQuery load method and injected into the modal dialog body.
 *
 * @cfg {String} [extraClass=""]
 * Custom class name which should be added to the modal dialog container.
 *
 * @cfg {Object} data
 *
 * @cfg {String|Function} data.title
 * Modal dialog title (can contain HTML tags).
 * If function passed in this parameter, the function result will be used.
 *
 * @cfg {String|Function} data.body
 * The main content of the dialog (can contain HTML tags).
 * If function passed in this parameter, the function result will be used.
 *
 * @cfg {Array} data.buttons
 * You can specify the custom buttons in this parameter which should be displayed in the modal footer.
 * Each array element is the object with the following parameters:
 * 	title      - button title.
 * 	extraClass - custom class name which will be added to the button.
 * 	handler    - function which will be called when button is clicked.
 *
 * @cfg {Number} [width=null]
 * Modal dialog width.
 *
 * @cfg {Number} [height=null]
 * Modal dialog height.
 *
 * @cfg {Number} [padding=null]
 * Modal dialog padding.
 *
 * @cfg {Boolean} [footer=true]
 * Display modal dialog footer.
 * Should be true if you want to display custom buttons.
 *
 * @cfg {Boolean} [header=true]
 * Display modal header.
 *
 * @cfg {Boolean} [fade=false]
 * Apply a CSS fade transition.
 */
Echo.GUI.Modal = Echo.Utils.inherit(Echo.GUI, function(config) {
	Echo.GUI.call(this, config, {
		"targetless": true,
		"show": false,
		"backdrop": true,
		"keyboard": true,
		"closeButton": true,
		"remote": false,
		"extraClass": "",
		"data": {},
		"width": null,
		"height": null,
		"padding": null,
		"footer": true,
		"header": true,
		"onHide": function() {},
		"onShow": function() {},
		"fade": false
	});
});

Echo.GUI.Modal.prototype.refresh = function() {
	var self = this;
	if (this.rendered) this.destroy();

	this.rendered = true;
	var css = this.config.get("fade") ? "fade" : "hide";
	this.element = this.element || $('
');
	this.element.empty();

	this._assembleHeader();
	this._assembleBody();
	this._assembleFooter();
	this._assembleBackdrop();

	this.element.on("hidden", function() {
		self.destroy();
	});
	this.element.on("shown", function() {
		self.config.get("onShow").call(self, self.element);
	});

	this.config.get("fade") && $.support.transition && this.element.one($.support.transition.end, function() {
		self.element.focus().trigger("shown");
	});

	this.element.appendTo("body")
		.wrap('
')
		.modal(this.config.getAsHash());

	// if we try show few modals in one time, then event "focusin.modal" be called infinity times
	$(document).off("focusin.modal");
};

/**
 * Shows the modal dialog.
 * 	myModal.show();
 */
Echo.GUI.Modal.prototype.show = function() {
	this.element.modal("show");
};

/**
 * Hides the modal dialog and removes the dialog instance.
 *  myModal.destroy();
 */
Echo.GUI.Modal.prototype.destroy = function() {
	if (this.rendered) {
		this.element.modal("hide");
		this.rendered = false;
		this.backdrop && this.backdrop.remove();
		this.element.find("iframe").hide().attr("src", "//about:blank").end().empty();
		this.element.unwrap().remove();
		this.config.get("onHide").call(this, this.element);
	}
};

/**
 * Hides the modal dialog.
 * 	myModal.hide();
 */
Echo.GUI.Modal.prototype.hide = function() {
	if (this.rendered) {
		this.element.modal("hide");
	}
};

Echo.GUI.Modal.prototype._assembleHeader = function() {
	if (this.config.get("header")) {
		var header = this._addSection("modal-header", $("
").append(this.config.get("data").title));
		if (this.config.get("closeButton")) {
			$('<button aria-hidden="true" data-dismiss="modal" class="close" type="button">')
				.append("×").prependTo(header);
		}
	}
}

Echo.GUI.Modal.prototype._assembleBody = function() {
	var self = this;
	var body = this._addSection("modal-body", this.config.get("data").body);
	if (this.config.get("href")) {
		this.element.one("shown", function() {
			body.append('');
		});
	}
	if (this.config.get("width") !== null) {
		this.element.width(this.config.get("width"))
			.css({"margin-left": this.config.get("width") * -0.5});
	}
	if (this.config.get("height") !== null) {
		body.height(this.config.get("height"));
	}
	if (this.config.get("padding") !== null) {
		body.css("padding", this.config.get("padding"));
	}
	if (this.config.get("extraClass")) {
		this.element.addClass(this.config.get("extraClass"));
	}
}

Echo.GUI.Modal.prototype._assembleFooter = function() {
	var self = this;
	if (this.config.get("footer")) {
		var footer = this._addSection("modal-footer");
		if (this.config.get("data").buttons) {
			$.map(this.config.get("data").buttons, function(button) {
				var el = $("<button>").addClass("btn").append(button.title);
				if (button.extraClass) {
					el.addClass(button.extraClass);
				}
				el.click(function() {
					button.handler && button.handler.call(self, this);
				});
				el.appendTo(footer);
			});
		}
	}
}

Echo.GUI.Modal.prototype._assembleBackdrop = function() {
	var self = this;
	if (self.config.get("backdrop")) {
		if (self.config.get("fade")) {
			self.element.on("show", function() {
				var modal = self.element.data("modal");
				var shown = modal.isShown;
				var backdrop = modal.options.backdrop;

				modal.options.backdrop = true;
				modal.isShown = true;
				modal.backdrop(function(){});
				modal.isShown = shown;
				modal.options.backdrop = backdrop;

				self.element.addClass("in");

				self.backdrop = modal.$backdrop.wrap('
').parent();
			});
		} else {
			self.element.on("shown", function() {
				self.backdrop = self.element.data("modal").$backdrop.wrap('
').parent();
			});
		}
		// we used manual control for backdrop if fade is true
		if (self.config.get("fade")) {
			self.config.set("backdrop", false);
		}
	}
}

Echo.GUI.Modal.prototype._addSection = function(css, content) {
	var section = $('
');
	this.element.append(section);
	if (content) {
		section.append($.isFunction(content) ? content(section) : content);
	}
	return section;
};

})(Echo.jQuery);
<|endoftext|>


<commit_before>/*
 * Konami-JS
 * http://snaptortoise.com/konami-js
 * Copyright (c) 2009 George Mandis (georgemandis.com)
 * Version: 1.0 (04/27/2009)
 * Licensed under the Artistic License/GPL
 * http://dev.perl.org/licenses/
 * Tested in: Safari 4, Firefox 3, IE7
 */

var konami = {
	input:"",
	clear:setTimeout('konami.clear_input()',2000),
	load: function(link) {
		window.document.onkeydown = function(e) {
			konami.input+= e.keyCode
			if (konami.input == "3838404037393739656613") {
				konami.code(link)
				clearTimeout(konami.clear)
				}
			clearTimeout(konami.clear)
			konami.clear = setTimeout("konami.clear_input()",2000)
			}
	},
	code: function(link) { window.location=link},
	clear_input: function() {
		konami.input="";
		clearTimeout(konami.clear);
	}
}
<commit_msg>I screwed up the code again.  Wow.  BA start, not AB star.<commit_after>/*
 * Konami-JS
 * http://snaptortoise.com/konami-js
 * Copyright (c) 2009 George Mandis (georgemandis.com)
 * Version: 1.0 (04/27/2009)
 * Licensed under the Artistic License/GPL
 * http://dev.perl.org/licenses/
 * Tested in: Safari 4, Firefox 3, IE7
 */

var konami = {
	input:"",
	clear:setTimeout('konami.clear_input()',2000),
	load: function(link) {
		window.document.onkeydown = function(e) {
			konami.input+= e.keyCode
			if (konami.input == "3838404037393739666513") {
				konami.code(link)
				clearTimeout(konami.clear)
				return
				}
			clearTimeout(konami.clear)
			konami.clear = setTimeout("konami.clear_input()",2000)
			}
	},
	code: function(link) { window.location=link},
	clear_input: function() {
		konami.input="";
		clearTimeout(konami.clear);
	}
}
<|endoftext|>


<commit_before>/*
	* Konami-JS ~
	* :: Now with support for touch events and multiple instances for
	* :: those situations that call for multiple easter eggs!
	* Code: http://konami-js.googlecode.com/
	* Examples: http://www.snaptortoise.com/konami-js
	* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
	* Version: 1.4.0 (1/21/2013)
	* Licensed under the GNU General Public License v3
	* http://www.gnu.org/copyleft/gpl.html
	* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser
*/

var Konami = function(callback) {
	var konami= {
			addEvent:function ( obj, type, fn, ref_obj )
			{
				if (obj.addEventListener)
					obj.addEventListener( type, fn, false );
				else if (obj.attachEvent)
				{
					// IE
					obj["e"+type+fn] = fn;
					obj[type+fn] = function() { obj["e"+type+fn]( window.event,ref_obj ); }
					obj.attachEvent( "on"+type, obj[type+fn] );
				}
			},
	        input:"",
	        pattern:"3838404037393739666513",
	        load: function(link) {
				this.addEvent(document,"keydown", function(e,ref_obj) {
					if (ref_obj) konami = ref_obj; // IE
					konami.input+= e ? e.keyCode : event.keyCode;
					if (konami.input.length > konami.pattern.length) konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
					if (konami.input == konami.pattern) {
                    konami.code(link);
					konami.input="";
                   	return;
                    }
            	},this);
           this.iphone.load(link);

				},
	        code: function(link) { window.location=link},
	        iphone:{
	                start_x:0,
	                start_y:0,
	                stop_x:0,
	                stop_y:0,
	                tap:false,
	                capture:false,
					orig_keys:"",
	                keys:["UP","UP","DOWN","DOWN","LEFT","RIGHT","LEFT","RIGHT","TAP","TAP","TAP"],
	                code: function(link) { konami.code(link);},
	                load: function(link){
									this.orig_keys = this.keys;
	    							konami.addEvent(document,"touchmove",function(e){
	                          if(e.touches.length == 1 && konami.iphone.capture==true){
	                            var touch = e.touches[0];
	                                konami.iphone.stop_x = touch.pageX;
	                                konami.iphone.stop_y = touch.pageY;
	                                konami.iphone.tap = false;
	                                konami.iphone.capture=false;
	                                konami.iphone.check_direction();
	                                }
	                                });
	                        konami.addEvent(document,"touchend",function(evt){
	                                if (konami.iphone.tap==true) konami.iphone.check_direction(link);
	                                },false);
	                        konami.addEvent(document,"touchstart", function(evt){
	                                konami.iphone.start_x = evt.changedTouches[0].pageX;
	                                konami.iphone.start_y = evt.changedTouches[0].pageY;
	                                konami.iphone.tap = true;
	                                konami.iphone.capture = true;
	                                });
	                                },
	                check_direction: function(link){
	                        x_magnitude = Math.abs(this.start_x-this.stop_x);
	                        y_magnitude = Math.abs(this.start_y-this.stop_y);
	                        x = ((this.start_x-this.stop_x) < 0) ? "RIGHT" : "LEFT";
	                        y = ((this.start_y-this.stop_y) < 0) ? "DOWN" : "UP";
	                        result = (x_magnitude > y_magnitude) ? x : y;
	                        result = (this.tap==true) ? "TAP" : result;

	                        if (result==this.keys[0]) this.keys = this.keys.slice(1,this.keys.length);
	                        if (this.keys.length==0) {
								this.keys=this.orig_keys;
								this.code(link);
								}
	                        }
	                }
	}

	typeof callback === "string" && konami.load(callback);
	if(typeof callback === "function")  {
		konami.code = callback;
		konami.load();
	}

	return konami;
}<commit_msg>Added semicolons so some JS minifiers don't choke per: https://github.com/snaptortoise/konami-js/issues/4<commit_after>/*
	* Konami-JS ~
	* :: Now with support for touch events and multiple instances for
	* :: those situations that call for multiple easter eggs!
	* Code: http://konami-js.googlecode.com/
	* Examples: http://www.snaptortoise.com/konami-js
	* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
	* Version: 1.4.1 (3/1//2013)
	* Licensed under the GNU General Public License v3
	* http://www.gnu.org/copyleft/gpl.html
	* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser
*/

var Konami = function(callback) {
	var konami= {
			addEvent:function ( obj, type, fn, ref_obj )
			{
				if (obj.addEventListener)
					obj.addEventListener( type, fn, false );
				else if (obj.attachEvent)
				{
					// IE
					obj["e"+type+fn] = fn;
					obj[type+fn] = function() { obj["e"+type+fn]( window.event,ref_obj ); }
					obj.attachEvent( "on"+type, obj[type+fn] );
				}
			},
	        input:"",
	        pattern:"3838404037393739666513",
	        load: function(link) {
				this.addEvent(document,"keydown", function(e,ref_obj) {
					if (ref_obj) konami = ref_obj; // IE
					konami.input+= e ? e.keyCode : event.keyCode;
					if (konami.input.length > konami.pattern.length) konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
					if (konami.input == konami.pattern) {
                    konami.code(link);
					konami.input="";
                   	return;
                    }
            	},this);
           this.iphone.load(link);

				},
	        code: function(link) { window.location=link},
	        iphone:{
	                start_x:0,
	                start_y:0,
	                stop_x:0,
	                stop_y:0,
	                tap:false,
	                capture:false,
					orig_keys:"",
	                keys:["UP","UP","DOWN","DOWN","LEFT","RIGHT","LEFT","RIGHT","TAP","TAP","TAP"],
	                code: function(link) { konami.code(link);},
	                load: function(link){
									this.orig_keys = this.keys;
	    							konami.addEvent(document,"touchmove",function(e){
	                          if(e.touches.length == 1 && konami.iphone.capture==true){
	                            var touch = e.touches[0];
	                                konami.iphone.stop_x = touch.pageX;
	                                konami.iphone.stop_y = touch.pageY;
	                                konami.iphone.tap = false;
	                                konami.iphone.capture=false;
	                                konami.iphone.check_direction();
	                                }
	                                });
	                        konami.addEvent(document,"touchend",function(evt){
	                                if (konami.iphone.tap==true) konami.iphone.check_direction(link);
	                                },false);
	                        konami.addEvent(document,"touchstart", function(evt){
	                                konami.iphone.start_x = evt.changedTouches[0].pageX;
	                                konami.iphone.start_y = evt.changedTouches[0].pageY;
	                                konami.iphone.tap = true;
	                                konami.iphone.capture = true;
	                                });
	                                },
	                check_direction: function(link){
	                        x_magnitude = Math.abs(this.start_x-this.stop_x);
	                        y_magnitude = Math.abs(this.start_y-this.stop_y);
	                        x = ((this.start_x-this.stop_x) < 0) ? "RIGHT" : "LEFT";
	                        y = ((this.start_y-this.stop_y) < 0) ? "DOWN" : "UP";
	                        result = (x_magnitude > y_magnitude) ? x : y;
	                        result = (this.tap==true) ? "TAP" : result;

	                        if (result==this.keys[0]) this.keys = this.keys.slice(1,this.keys.length);
	                        if (this.keys.length==0) {
								this.keys=this.orig_keys;
								this.code(link);
								}
	                        }
	                }
	}

	typeof callback === "string" && konami.load(callback);
	if(typeof callback === "function")  {
		konami.code = callback;
		konami.load();
	}

	return konami;
}
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var CardboardVRDisplay = require('./cardboard-vr-display.js');
var MouseKeyboardVRDisplay = require('./mouse-keyboard-vr-display.js');
// Uncomment to add positional tracking via webcam.
//var WebcamPositionSensorVRDevice = require('./webcam-position-sensor-vr-device.js');
var VRDisplay = require('./base.js').VRDisplay;
var HMDVRDevice = require('./base.js').HMDVRDevice;
var PositionSensorVRDevice = require('./base.js').PositionSensorVRDevice;
var VRDisplayHMDDevice = require('./display-wrappers.js').VRDisplayHMDDevice;
var VRDisplayPositionSensorDevice = require('./display-wrappers.js').VRDisplayPositionSensorDevice;

function WebVRPolyfill() {
  this.displays = [];
  this.devices = []; // For deprecated objects
  this.devicesPopulated = false;
  this.nativeWebVRAvailable = this.isWebVRAvailable();
  this.nativeLegacyWebVRAvailable = this.isDeprecatedWebVRAvailable();

  if (!this.nativeLegacyWebVRAvailable) {
    if (!this.nativeWebVRAvailable) {
      this.enablePolyfill();
    }
    if (WebVRConfig.ENABLE_DEPRECATED_API) {
      this.enableDeprecatedPolyfill();
    }
  }
}

WebVRPolyfill.prototype.isWebVRAvailable = function() {
  return ('getVRDisplays' in navigator);
};

WebVRPolyfill.prototype.isDeprecatedWebVRAvailable = function() {
  return ('getVRDevices' in navigator) || ('mozGetVRDevices' in navigator);
};

WebVRPolyfill.prototype.populateDevices = function() {
  if (this.devicesPopulated) {
    return;
  }

  // Initialize our virtual VR devices.
  var vrDisplay = null;

  // Add a Cardboard VRDisplay on compatible mobile devices
  if (this.isCardboardCompatible()) {
    vrDisplay = new CardboardVRDisplay();
    this.displays.push(vrDisplay);

    // For backwards compatibility
    if (WebVRConfig.ENABLE_DEPRECATED_API) {
      this.devices.push(new VRDisplayHMDDevice(vrDisplay));
      this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
    }
  }

  // Add a Mouse and Keyboard driven VRDisplay for desktops/laptops
  if (!this.isMobile() && !WebVRConfig.MOUSE_KEYBOARD_CONTROLS_DISABLED) {
    vrDisplay = new MouseKeyboardVRDisplay();
    this.displays.push(vrDisplay);

    // For backwards compatibility
    if (WebVRConfig.ENABLE_DEPRECATED_API) {
      this.devices.push(new VRDisplayHMDDevice(vrDisplay));
      this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
    }
  }

  // Uncomment to add positional tracking via webcam.
  //if (!this.isMobile() && WebVRConfig.ENABLE_DEPRECATED_API) {
  //  positionDevice = new WebcamPositionSensorVRDevice();
  //  this.devices.push(positionDevice);
  //}

  this.devicesPopulated = true;
};

WebVRPolyfill.prototype.enablePolyfill = function() {
  // Provide navigator.getVRDisplays.
  navigator.getVRDisplays = this.getVRDisplays.bind(this);

  // Provide the VRDisplay object.
  window.VRDisplay = VRDisplay;

  // Provide vrEnabled.
  var that = this;
  Object.defineProperty(navigator, 'vrEnabled', {
    get: function () {
      return that.isCardboardCompatible() &&
        (document.fullscreenEnabled ||
          document.mozFullScreenEnabled ||
          document.webkitFullscreenEnabled ||
          false);
    }
  });
};

WebVRPolyfill.prototype.enableDeprecatedPolyfill = function() {
  // Provide navigator.getVRDevices.
  navigator.getVRDevices = this.getVRDevices.bind(this);

  // Provide the CardboardHMDVRDevice and PositionSensorVRDevice objects.
  window.HMDVRDevice = HMDVRDevice;
  window.PositionSensorVRDevice = PositionSensorVRDevice;
};

WebVRPolyfill.prototype.getVRDisplays = function() {
  this.populateDevices();
  var displays = this.displays;
  return new Promise(function(resolve, reject) {
    try {
      resolve(displays);
    } catch (e) {
      reject(e);
    }
  });
};

WebVRPolyfill.prototype.getVRDevices = function() {
  console.warn('getVRDevices is deprecated. Please update your code to use getVRDisplays instead.');
  var self = this;
  return new Promise(function(resolve, reject) {
    try {
      if (!self.devicesPopulated) {
        if (self.nativeWebVRAvailable) {
          return navigator.getVRDisplays(function(displays) {
            for (var i = 0; i < displays.length; ++i) {
              self.devices.push(new VRDisplayHMDDevice(displays[i]));
              self.devices.push(new VRDisplayPositionSensorDevice(displays[i]));
            }
            self.devicesPopulated = true;
            resolve(self.devices);
          }, reject);
        }

        if (self.nativeLegacyWebVRAvailable) {
          return (navigator.getVRDDevices || navigator.mozGetVRDevices)(function(devices) {
            for (var i = 0; i < devices.length; ++i) {
              if (devices[i] instanceof HMDVRDevice) {
                self.devices.push(devices[i]);
              }
              if (devices[i] instanceof PositionSensorVRDevice) {
                self.devices.push(devices[i]);
              }
            }
            self.devicesPopulated = true;
            resolve(self.devices);
          }, reject);
        }
      }

      self.populateDevices();
      resolve(self.devices);
    } catch (e) {
      reject(e);
    }
  });
};

/**
 * Determine if a device is mobile.
 */
WebVRPolyfill.prototype.isMobile = function() {
  return /Android/i.test(navigator.userAgent) ||
      /iPhone|iPad|iPod/i.test(navigator.userAgent);
};

WebVRPolyfill.prototype.isCardboardCompatible = function() {
  // For now, support all iOS and Android devices.
  // Also enable the WebVRConfig.FORCE_VR flag for debugging.
  return this.isMobile() || WebVRConfig.FORCE_ENABLE_VR;
};

module.exports = WebVRPolyfill;
<commit_msg>More descriptive comment for vrEnabled #158<commit_after>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var CardboardVRDisplay = require('./cardboard-vr-display.js');
var MouseKeyboardVRDisplay = require('./mouse-keyboard-vr-display.js');
// Uncomment to add positional tracking via webcam.
//var WebcamPositionSensorVRDevice = require('./webcam-position-sensor-vr-device.js');
var VRDisplay = require('./base.js').VRDisplay;
var HMDVRDevice = require('./base.js').HMDVRDevice;
var PositionSensorVRDevice = require('./base.js').PositionSensorVRDevice;
var VRDisplayHMDDevice = require('./display-wrappers.js').VRDisplayHMDDevice;
var VRDisplayPositionSensorDevice = require('./display-wrappers.js').VRDisplayPositionSensorDevice;

function WebVRPolyfill() {
  this.displays = [];
  this.devices = []; // For deprecated objects
  this.devicesPopulated = false;
  this.nativeWebVRAvailable = this.isWebVRAvailable();
  this.nativeLegacyWebVRAvailable = this.isDeprecatedWebVRAvailable();

  if (!this.nativeLegacyWebVRAvailable) {
    if (!this.nativeWebVRAvailable) {
      this.enablePolyfill();
    }
    if (WebVRConfig.ENABLE_DEPRECATED_API) {
      this.enableDeprecatedPolyfill();
    }
  }
}

WebVRPolyfill.prototype.isWebVRAvailable = function() {
  return ('getVRDisplays' in navigator);
};

WebVRPolyfill.prototype.isDeprecatedWebVRAvailable = function() {
  return ('getVRDevices' in navigator) || ('mozGetVRDevices' in navigator);
};

WebVRPolyfill.prototype.populateDevices = function() {
  if (this.devicesPopulated) {
    return;
  }

  // Initialize our virtual VR devices.
  var vrDisplay = null;

  // Add a Cardboard VRDisplay on compatible mobile devices
  if (this.isCardboardCompatible()) {
    vrDisplay = new CardboardVRDisplay();
    this.displays.push(vrDisplay);

    // For backwards compatibility
    if (WebVRConfig.ENABLE_DEPRECATED_API) {
      this.devices.push(new VRDisplayHMDDevice(vrDisplay));
      this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
    }
  }

  // Add a Mouse and Keyboard driven VRDisplay for desktops/laptops
  if (!this.isMobile() && !WebVRConfig.MOUSE_KEYBOARD_CONTROLS_DISABLED) {
    vrDisplay = new MouseKeyboardVRDisplay();
    this.displays.push(vrDisplay);

    // For backwards compatibility
    if (WebVRConfig.ENABLE_DEPRECATED_API) {
      this.devices.push(new VRDisplayHMDDevice(vrDisplay));
      this.devices.push(new VRDisplayPositionSensorDevice(vrDisplay));
    }
  }

  // Uncomment to add positional tracking via webcam.
  //if (!this.isMobile() && WebVRConfig.ENABLE_DEPRECATED_API) {
  //  positionDevice = new WebcamPositionSensorVRDevice();
  //  this.devices.push(positionDevice);
  //}

  this.devicesPopulated = true;
};

WebVRPolyfill.prototype.enablePolyfill = function() {
  // Provide navigator.getVRDisplays.
  navigator.getVRDisplays = this.getVRDisplays.bind(this);

  // Provide the VRDisplay object.
  window.VRDisplay = VRDisplay;

  // Provide navigator.vrEnabled.
  var that = this;
  Object.defineProperty(navigator, 'vrEnabled', {
    get: function () {
      return that.isCardboardCompatible() &&
        (document.fullscreenEnabled ||
          document.mozFullScreenEnabled ||
          document.webkitFullscreenEnabled ||
          false);
    }
  });
};

WebVRPolyfill.prototype.enableDeprecatedPolyfill = function() {
  // Provide navigator.getVRDevices.
  navigator.getVRDevices = this.getVRDevices.bind(this);

  // Provide the CardboardHMDVRDevice and PositionSensorVRDevice objects.
  window.HMDVRDevice = HMDVRDevice;
  window.PositionSensorVRDevice = PositionSensorVRDevice;
};

WebVRPolyfill.prototype.getVRDisplays = function() {
  this.populateDevices();
  var displays = this.displays;
  return new Promise(function(resolve, reject) {
    try {
      resolve(displays);
    } catch (e) {
      reject(e);
    }
  });
};

WebVRPolyfill.prototype.getVRDevices = function() {
  console.warn('getVRDevices is deprecated. Please update your code to use getVRDisplays instead.');
  var self = this;
  return new Promise(function(resolve, reject) {
    try {
      if (!self.devicesPopulated) {
        if (self.nativeWebVRAvailable) {
          return navigator.getVRDisplays(function(displays) {
            for (var i = 0; i < displays.length; ++i) {
              self.devices.push(new VRDisplayHMDDevice(displays[i]));
              self.devices.push(new VRDisplayPositionSensorDevice(displays[i]));
            }
            self.devicesPopulated = true;
            resolve(self.devices);
          }, reject);
        }

        if (self.nativeLegacyWebVRAvailable) {
          return (navigator.getVRDDevices || navigator.mozGetVRDevices)(function(devices) {
            for (var i = 0; i < devices.length; ++i) {
              if (devices[i] instanceof HMDVRDevice) {
                self.devices.push(devices[i]);
              }
              if (devices[i] instanceof PositionSensorVRDevice) {
                self.devices.push(devices[i]);
              }
            }
            self.devicesPopulated = true;
            resolve(self.devices);
          }, reject);
        }
      }

      self.populateDevices();
      resolve(self.devices);
    } catch (e) {
      reject(e);
    }
  });
};

/**
 * Determine if a device is mobile.
 */
WebVRPolyfill.prototype.isMobile = function() {
  return /Android/i.test(navigator.userAgent) ||
      /iPhone|iPad|iPod/i.test(navigator.userAgent);
};

WebVRPolyfill.prototype.isCardboardCompatible = function() {
  // For now, support all iOS and Android devices.
  // Also enable the WebVRConfig.FORCE_VR flag for debugging.
  return this.isMobile() || WebVRConfig.FORCE_ENABLE_VR;
};

module.exports = WebVRPolyfill;
<|endoftext|>