<commit_before>define(["src/class"], function (createClass) {
	"use strict";

	describe("createClass", function () {
		var Foo = createClass(function () {
				this.id = 1;
			});
		it("creates a class constructor", function () {
			expect(typeof Foo).toBe("function");
			expect(typeof Foo.prototype).toBe("object");
		});
		it("can be called or without either optional params (options, init)", function () {
			var Bar = createClass(),
				Hello = createClass({
					name: "Bar"
				});
			expect(typeof Bar).toBe("function");
			expect(typeof Bar.prototype).toBe("object");
			expect(typeof Hello).toBe("function");
			expect(typeof Hello.prototype).toBe("object");
		});
		it("invokes passed in init function", function () {
			var foo = new Foo();
			expect(foo.id).toBe(1);
		});
		it("allows new to be optional", function () {
			var bar = createClass(),
				barInstance = bar();
			expect(barInstance instanceof bar).toBe(true);
		});
	});
});
<commit_msg>finishing out testing createClass capabilities<commit_after>define(["src/class"], function (createClass) {
	"use strict";

	describe("createClass()", function () {
		it("creates a class constructor", function () {
			var Foo = createClass(function () {
				this.id = 1;
			});
			useClass(Foo);
		});

		it("can be called without params", function () {
			var Foo = createClass();
			useClass(Foo);
		});

		it("can be called without options param", function () {
			var Foo = createClass(function () {});
			useClass(Foo);
		});

		it("can be called without init param", function () {
			var Foo = createClass({
				name: "Foo"
			});
			useClass(Foo);
		});

		it("can be called passing both options and init", function () {
			var Foo = createClass({}, function () {});
			useClass(Foo);
		});
	});

	describe("classes created with createClass()", function () {
		it("can extend a super class", function () {
			var Foo = createClass({
					name: "Foo"
				}, fooInit),
				Bar = createClass({
					"extends": Foo
				});
			function fooInit () {}
			useClass(Foo);
			useClass(Bar);
			expect(new Bar() instanceof Foo).toBe(true);
		});

		it("can be decorated with a mixins object", function () {
			var Foo = createClass({
					name: "Foo"
				}, fooInit),
				mixin = {
					sayHello: function () {
						return "Hello";
					}
				},
				Bar = createClass({
					"extends": Foo,
					"mixins": mixin
				});
			function fooInit () {}
			useClass(Foo);
			useClass(Bar);
			expect(new Bar() instanceof Foo).toBe(true);
			expect(new Bar().sayHello()).toBe("Hello");
		});

		it("can be decorated with a mixins array", function () {
			var Foo = createClass({
					name: "Foo"
				}),
				mixins = [{
					sayHello: function () {
						return "Hello";
					}
				}, {
					getId: function () {
						return this.id;
					}
				}],
				Bar = createClass({
					"extends": Foo,
					"mixins": mixins
				}, initBar);
			function initBar () {
				this.id = 1;
			}
			useClass(Foo);
			useClass(Bar);
			expect(new Bar() instanceof Foo).toBe(true);
			expect(new Bar().sayHello()).toBe("Hello");
			expect(new Bar().getId()).toBe(1);
		});

		it("can be decorated with a mixins class", function () {
			var Foo,
				Bar = createClass();
			Bar.prototype.getId = function () {
				return this.id;
			};
			Foo = createClass({
				name: "Foo",
				mixins: Bar
			}, fooInit);
			function fooInit () {
				this.id = 1;
			}
			useClass(Foo);
			useClass(Bar);
			expect(new Foo().getId()).toBe(1);
		});
	});

	describe("class constructors created with createClass()", function () {
		it("are inject with class name", function () {
			var Foo = createClass({
				name: "Foo"
			}, function () {});
			expect(Foo.toString().indexOf("function Foo")).toBe(0);
		});

		it("invoke init function", function () {
			var Foo = createClass(function () {
					this.id = 1;
				}),
				foo = new Foo();
			useClass(Foo);
			expect(foo.id).toBe(1);
		});

		it("can be called with or with new operator", function () {
			var foo = createClass(),
				fooInstance = foo();
			expect(fooInstance instanceof foo).toBe(true);
		});
	});

	function useClass (Constructor) {
		expect(typeof Constructor).toBe("function");
		expect(typeof Constructor.prototype).toBe("object");
		expect(new Constructor() instanceof Constructor).toBe(true);
	}
});
<|endoftext|>


<commit_before>import Observer from './Observer.js';

/**
 * Fix space key for editable summary elements
 */
export default class SummaryObserver extends Observer {
    /**
     * @inheritDoc
     */
    observe(ev) {
        ev.forEach(item => item.addedNodes.forEach(node => {
            if (node instanceof HTMLElement && node.tagName.toLowerCase() === 'summary'
                || node instanceof HTMLDetailsElement && (node = node.querySelector('summary'))
            ) {
                const call = () => {
                    if (!node.innerText.trim()) {
                        node.innerText = 'Details';
                    }
                };
                call();
                node.addEventListener('blur', call);
                node.addEventListener('keydown', ev => {
                    if (ev.key === ' ') {
                        ev.preventDefault();
                    }
                });
                node.addEventListener('keyup', ev => {
                    if (ev.key === ' ') {
                        ev.preventDefault();
                        this.editor.insertText(' ');
                    }
                });
            }
        }));
    }
}
<commit_msg>Clean up<commit_after>import Observer from './Observer.js';

/**
 * Fix space key for editable summary elements
 */
export default class SummaryObserver extends Observer {
    /**
     * @inheritDoc
     */
    observe(ev) {
        ev.forEach(item => item.addedNodes.forEach(node => {
            if (node instanceof HTMLElement && node.tagName.toLowerCase() === 'summary' || node instanceof HTMLDetailsElement && (node = node.querySelector('summary'))) {
                const call = () => {
                    if (!node.innerText.trim()) {
                        node.innerText = 'Details';
                    }
                };
                call();
                node.addEventListener('blur', call);
                node.addEventListener('keydown', ev => {
                    if (ev.key === ' ') {
                        ev.preventDefault();
                    }
                });
                node.addEventListener('keyup', ev => {
                    if (ev.key === ' ') {
                        ev.preventDefault();
                        this.editor.insertText(' ');
                    }
                });
            }
        }));
    }
}
<|endoftext|>


<commit_before>//Server side of the ChatApplication
//Server shows the ChatWidget, but it's not able to use it to send own messages
function ServerControl ()
{
	me.Action("ClientSendMessage").Triggered.connect(this, this.ClientMessage);
	me.Action("ServerSendMessage").Triggered.connect(empty); //Need to register action on Server, but Client is the only one to handle this action
}

//Receive incoming messages from client
//and pass it to all clients
ServerControl.prototype.ClientMessage = function(sender, msg)
{
	var message = (sender + " : " + msg);
	chatLog.append(message);
	me.Exec(4, "ServerSendMessage", message);
}

//Client side of the ChatApplication
function ClientControl (userName)
{
	this.name = userName;
	lineEdit.returnPressed.connect(this, this.SendMessage);
	me.Action("ServerSendMessage").Triggered.connect(this, this.ReceiveServerMessage);
	me.Exec(2, "ClientSendMessage", this.name, "Joined Chat");

}

ClientControl.prototype.SendMessage = function()
{
	var msg = lineEdit.text;
	me.Exec(2, "ClientSendMessage", this.name, msg);
	lineEdit.text = "";
}

ClientControl.prototype.ReceiveServerMessage = function(msg)
{
	chatLog.append(msg);
}

function empty()
{
}

print("Loading ChatApplication");

var widget = ui.LoadFromFile(".\\scenes\\ChatApplication\\ChatWidget.ui", false);
var chatLog = widget.findChild("chatLog");
var lineEdit = widget.findChild("lineEdit");
var proxy = new UiProxyWidget(widget);
ui.AddWidgetToScene(proxy);
proxy.x = 10;
proxy.y = 10;
proxy.visible = false;
proxy.windowFlags = 0;

var chatControl;
server = me.GetComponentRaw("EC_DynamicComponent", "server");

if (server)
{
	print("Starting Chat Server");
	chatControl = new ServerControl();
	proxy.visible = true;
}
else
{
	print("Starting Chat Client");

	//Insert Username from widget. In future this is replaced
	var joinWidget = ui.LoadFromFile(".\\scenes\\ChatApplication\\JoinWidget.ui", false);
	var btn = joinWidget.findChild("pushJoin");
	var lineUser = joinWidget.findChild("lineUserName");

	var joinProxy = new UiProxyWidget(joinWidget);
	ui.AddWidgetToScene(joinProxy);
	joinProxy.x = 50;
	joinProxy.y = 50;
	joinProxy.visible = true;
	joinProxy.windowFlags = 0;

	btn.pressed.connect(CreateUser);
	lineUser.returnPressed.connect(CreateUser);

	me.Action("ClientSendMessage").Triggered.connect(empty);  //Need to register action on Client, but Server is the only one to handle this action
}

function CreateUser()
{
	userName = lineUser.text;
	joinProxy.visible = false;
	proxy.visible = true;
	chatControl = new ClientControl(userName);
}

function OnScriptDestroyed()
{
	print("Closing ChatApplication");

	if(!server)
	{
		me.Exec(2, "ClientSendMessage", chatControl.name, " has left from the chat");
	}
}
<commit_msg>Fixed old code in ChatApplication.js to refer to uiservice. instead of ui.<commit_after>//Server side of the ChatApplication
//Server shows the ChatWidget, but it's not able to use it to send own messages
function ServerControl ()
{
	me.Action("ClientSendMessage").Triggered.connect(this, this.ClientMessage);
	me.Action("ServerSendMessage").Triggered.connect(empty); //Need to register action on Server, but Client is the only one to handle this action
}

//Receive incoming messages from client
//and pass it to all clients
ServerControl.prototype.ClientMessage = function(sender, msg)
{
	var message = (sender + " : " + msg);
	chatLog.append(message);
	me.Exec(4, "ServerSendMessage", message);
}

//Client side of the ChatApplication
function ClientControl (userName)
{
	this.name = userName;
	lineEdit.returnPressed.connect(this, this.SendMessage);
	me.Action("ServerSendMessage").Triggered.connect(this, this.ReceiveServerMessage);
	me.Exec(2, "ClientSendMessage", this.name, "Joined Chat");

}

ClientControl.prototype.SendMessage = function()
{
	var msg = lineEdit.text;
	me.Exec(2, "ClientSendMessage", this.name, msg);
	lineEdit.text = "";
}

ClientControl.prototype.ReceiveServerMessage = function(msg)
{
	chatLog.append(msg);
}

function empty()
{
}

print("Loading ChatApplication");

var widget = uiservice.LoadFromFile(".\\scenes\\ChatApplication\\ChatWidget.ui", false);
var chatLog = widget.findChild("chatLog");
var lineEdit = widget.findChild("lineEdit");
var proxy = new UiProxyWidget(widget);
uiservice.AddWidgetToScene(proxy);
proxy.x = 10;
proxy.y = 10;
proxy.visible = false;
proxy.windowFlags = 0;

var chatControl;
server = me.GetComponentRaw("EC_DynamicComponent", "server");

if (server)
{
	print("Starting Chat Server");
	chatControl = new ServerControl();
	proxy.visible = true;
}
else
{
	print("Starting Chat Client");

	//Insert Username from widget. In future this is replaced
	var joinWidget = uiservice.LoadFromFile(".\\scenes\\ChatApplication\\JoinWidget.ui", false);
	var btn = joinWidget.findChild("pushJoin");
	var lineUser = joinWidget.findChild("lineUserName");

	var joinProxy = new UiProxyWidget(joinWidget);
	uiservice.AddWidgetToScene(joinProxy);
	joinProxy.x = 50;
	joinProxy.y = 50;
	joinProxy.visible = true;
	joinProxy.windowFlags = 0;

	btn.pressed.connect(CreateUser);
	lineUser.returnPressed.connect(CreateUser);

	me.Action("ClientSendMessage").Triggered.connect(empty);  //Need to register action on Client, but Server is the only one to handle this action
}

function CreateUser()
{
	userName = lineUser.text;
	joinProxy.visible = false;
	proxy.visible = true;
	chatControl = new ClientControl(userName);
}

function OnScriptDestroyed()
{
	print("Closing ChatApplication");

	if(!server)
	{
		me.Exec(2, "ClientSendMessage", chatControl.name, " has left from the chat");
	}
}
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const path = require('path');
const hapi = require('@hapi/hapi');
const h2o2 = require('@hapi/h2o2');
const hapiPluginWebsocket = require('hapi-plugin-websocket');
const debugLog = require('./debugLog');
const createLambdaContext = require('./createLambdaContext');
const functionHelper = require('./functionHelper');
const { getUniqueId } = require('./utils');
const authFunctionNameExtractor = require('./authFunctionNameExtractor');
const wsHelpers = require('./websocketHelpers');

module.exports = class ApiGatewayWebSocket {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.service = serverless.service;
    this.serverlessLog = serverless.cli.log.bind(serverless.cli);
    this.options = options;
    this.exitCode = 0;
    this.clients = new Map();
    this.actions = {};
    this.websocketsApiRouteSelectionExpression = serverless.service.provider.websocketsApiRouteSelectionExpression || '$request.body.action';
  }

  _createWebSocket() {
    // start COPY PASTE FROM HTTP SERVER CODE
    const serverOptions = {
      host: this.options.host,
      port: this.options.websocketPort,
      router: {
        stripTrailingSlash: !this.options.preserveTrailingSlash, // removes trailing slashes on incoming paths.
      },
    };

    const httpsDir = this.options.httpsProtocol;

    // HTTPS support
    if (typeof httpsDir === 'string' && httpsDir.length > 0) {
      serverOptions.tls = {
        key: fs.readFileSync(path.resolve(httpsDir, 'key.pem'), 'ascii'),
        cert: fs.readFileSync(path.resolve(httpsDir, 'cert.pem'), 'ascii'),
      };
    }

    serverOptions.state = this.options.enforceSecureCookies ? {
      isHttpOnly: true,
      isSameSite: false,
      isSecure: true,
    } : {
      isHttpOnly: false,
      isSameSite: false,
      isSecure: false,
    };

    // Hapijs server creation
    this.wsServer = hapi.server(serverOptions);

    this.wsServer.register(h2o2).catch(err => err && this.serverlessLog(err));

    // Enable CORS preflight response
    this.wsServer.ext('onPreResponse', (request, h) => {
      if (request.headers.origin) {
        const response = request.response.isBoom ? request.response.output : request.response;

        response.headers['access-control-allow-origin'] = request.headers.origin;
        response.headers['access-control-allow-credentials'] = 'true';

        if (request.method === 'options') {
          response.statusCode = 200;
          response.headers['access-control-expose-headers'] = 'content-type, content-length, etag';
          response.headers['access-control-max-age'] = 60 * 10;

          if (request.headers['access-control-request-headers']) {
            response.headers['access-control-allow-headers'] = request.headers['access-control-request-headers'];
          }

          if (request.headers['access-control-request-method']) {
            response.headers['access-control-allow-methods'] = request.headers['access-control-request-method'];
          }
        }
      }

      return h.continue;
    });
    // end COPY PASTE FROM HTTP SERVER CODE

    this.wsServer.register(hapiPluginWebsocket).catch(err => err && this.serverlessLog(err));

    const doAction = (ws, connectionId, name, event, doDefaultAction) => {
      const sendError = err => {
        if (ws.readyState === /* OPEN */1) {
          ws.send(JSON.stringify({ message:'Internal server error', connectionId, requestId:'1234567890' }));
        }

        debugLog(`Error in handler of action ${action}`, err);
      };

      let action = this.actions[name];

      if (!action && doDefaultAction) action = this.actions.$default;
      if (!action) return;

      function cb(err) {
        if (!err) return;
        sendError(err);
      }

      // TEMP
      const func = {
        ...action.fun,
        name,
      };
      const context = createLambdaContext(func, this.service.provider, cb);

      let p = null;

      try {
        p = action.handler(event, context, cb);
      }
      catch (err) {
        sendError(err);
      }

      if (p) {
        p.catch(err => {
          sendError(err);
        });
      }
    };

    this.wsServer.route({
      method: 'POST',
      path: '/',
      config: {
        payload: {
          output: 'data',
          parse: true,
          allow: 'application/json',
        },
        plugins: {
          websocket: {
            only: true,
            initially: false,
            connect: ({ ws, req }) => {
              const queryStringParameters = parseQuery(req.url);
              const connection = { connectionId:getUniqueId(), connectionTime:Date.now() };

              debugLog(`connect:${connection.connectionId}`);

              this.clients.set(ws, connection);

              let event = wsHelpers.createConnectEvent('$connect', 'CONNECT', connection, this.options);

              if (Object.keys(queryStringParameters).length > 0) {
                event = { queryStringParameters, ...event };
              }

              doAction(ws, connection.connectionId, '$connect', event, false);
            },
            disconnect: ({ ws }) => {
              const connection = this.clients.get(ws);

              debugLog(`disconnect:${connection.connectionId}`);

              this.clients.delete(ws);

              const event = wsHelpers.createDisconnectEvent('$disconnect', 'DISCONNECT', connection, this.options);

              doAction(ws, connection.connectionId, '$disconnect', event, false);
            },
          },
        },
      },
      handler: (request, h) => {
        const { initially, ws } = request.websocket();

        if (!request.payload || initially) {
          return h.response().code(204);
        }

        const connection = this.clients.get(ws);
        let actionName = null;

        if (this.websocketsApiRouteSelectionExpression.startsWith('$request.body.')) {
          actionName = request.payload;

          if (typeof actionName === 'object') {
            this.websocketsApiRouteSelectionExpression
            .replace('$request.body.', '')
            .split('.')
            .forEach(key => {
              if (actionName) {
                actionName = actionName[key];
              }
            });
          }
          else actionName = null;
        }

        if (typeof actionName !== 'string') {
          actionName = null;
        }

        const action = actionName || '$default';

        debugLog(`action:${action} on connection=${connection.connectionId}`);

        const event = wsHelpers.createEvent(action, 'MESSAGE', connection, request.payload, this.options);

        doAction(ws, connection.connectionId, action, event, true);

        return h.response().code(204);
      },
    });

    this.wsServer.route({
      method: 'GET',
      path: '/{path*}',
      handler: (request, h) => h.response().code(426),
    });

    this.wsServer.route({
      method: 'POST',
      path: '/@connections/{connectionId}',
      config: { payload: { parse: false } },
      handler: (request, h) => {
        debugLog(`got POST to ${request.url}`);

        const getByConnectionId = (map, searchValue) => {
          for (const [key, connection] of map.entries()) {
            if (connection.connectionId === searchValue) return key;
          }

          return undefined;
        };

        const ws = getByConnectionId(this.clients, request.params.connectionId);

        if (!ws) return h.response().code(410);
        if (!request.payload) return '';

        ws.send(request.payload.toString());

        debugLog(`sent data to connection:${request.params.connectionId}`);

        return '';
      },
    });
  }

  _createWsAction(fun, funName, servicePath, funOptions, event) {
    let handler; // The lambda function
    Object.assign(process.env, this.originalEnvironment);

    try {
      if (this.options.noEnvironment) {
        // This evict errors in server when we use aws services like ssm
        const baseEnvironment = {
          AWS_REGION: 'dev',
        };

        if (!process.env.AWS_PROFILE) {
          baseEnvironment.AWS_ACCESS_KEY_ID = 'dev';
          baseEnvironment.AWS_SECRET_ACCESS_KEY = 'dev';
        }

        process.env = Object.assign(baseEnvironment, process.env);
      }
      else {
        Object.assign(
          process.env,
          { AWS_REGION: this.service.provider.region },
          this.service.provider.environment,
          this.service.functions[funName].environment
        );
      }

      process.env._HANDLER = fun.handler;
      handler = functionHelper.createHandler(funOptions, this.options);
    }
    catch (error) {
      return this.serverlessLog(`Error while loading ${funName}`, error);
    }

    const actionName = event.websocket.route;
    const action = { funName, fun, funOptions, servicePath, handler };

    this.actions[actionName] = action;
    this.serverlessLog(`Action '${event.websocket.route}'`);
  }

  _extractAuthFunctionName(endpoint) {
    const result = authFunctionNameExtractor(endpoint, this.serverlessLog);

    return result.unsupportedAuth ? null : result.authorizerName;
  }

  // All done, we can listen to incomming requests
  async _listen() {
    try {
      await this.wsServer.start();
    }
    catch (error) {
      console.error(`Unexpected error while starting serverless-offline websocket server on port ${this.options.websocketPort}:`, error);
      process.exit(1);
    }

    this.printBlankLine();
    this.serverlessLog(`Offline [websocket] listening on ws${this.options.httpsProtocol ? 's' : ''}://${this.options.host}:${this.options.websocketPort}`);
  }
};

function parseQuery(queryString) {
  const query = {};
  const parts = queryString.split('?');

  if (parts.length < 2) return {};

  const pairs = parts[1].split('&');

  pairs.forEach(pair => {
    const kv = pair.split('=');
    query[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || '');
  });

  return query;
}
<commit_msg>feat: add @connections DELETE route to close websocket<commit_after>'use strict';

const fs = require('fs');
const path = require('path');
const hapi = require('@hapi/hapi');
const h2o2 = require('@hapi/h2o2');
const hapiPluginWebsocket = require('hapi-plugin-websocket');
const debugLog = require('./debugLog');
const createLambdaContext = require('./createLambdaContext');
const functionHelper = require('./functionHelper');
const { getUniqueId } = require('./utils');
const authFunctionNameExtractor = require('./authFunctionNameExtractor');
const wsHelpers = require('./websocketHelpers');

module.exports = class ApiGatewayWebSocket {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.service = serverless.service;
    this.serverlessLog = serverless.cli.log.bind(serverless.cli);
    this.options = options;
    this.exitCode = 0;
    this.clients = new Map();
    this.actions = {};
    this.websocketsApiRouteSelectionExpression = serverless.service.provider.websocketsApiRouteSelectionExpression || '$request.body.action';
  }

  _createWebSocket() {
    // start COPY PASTE FROM HTTP SERVER CODE
    const serverOptions = {
      host: this.options.host,
      port: this.options.websocketPort,
      router: {
        stripTrailingSlash: !this.options.preserveTrailingSlash, // removes trailing slashes on incoming paths.
      },
    };

    const httpsDir = this.options.httpsProtocol;

    // HTTPS support
    if (typeof httpsDir === 'string' && httpsDir.length > 0) {
      serverOptions.tls = {
        key: fs.readFileSync(path.resolve(httpsDir, 'key.pem'), 'ascii'),
        cert: fs.readFileSync(path.resolve(httpsDir, 'cert.pem'), 'ascii'),
      };
    }

    serverOptions.state = this.options.enforceSecureCookies ? {
      isHttpOnly: true,
      isSameSite: false,
      isSecure: true,
    } : {
      isHttpOnly: false,
      isSameSite: false,
      isSecure: false,
    };

    // Hapijs server creation
    this.wsServer = hapi.server(serverOptions);

    this.wsServer.register(h2o2).catch(err => err && this.serverlessLog(err));

    // Enable CORS preflight response
    this.wsServer.ext('onPreResponse', (request, h) => {
      if (request.headers.origin) {
        const response = request.response.isBoom ? request.response.output : request.response;

        response.headers['access-control-allow-origin'] = request.headers.origin;
        response.headers['access-control-allow-credentials'] = 'true';

        if (request.method === 'options') {
          response.statusCode = 200;
          response.headers['access-control-expose-headers'] = 'content-type, content-length, etag';
          response.headers['access-control-max-age'] = 60 * 10;

          if (request.headers['access-control-request-headers']) {
            response.headers['access-control-allow-headers'] = request.headers['access-control-request-headers'];
          }

          if (request.headers['access-control-request-method']) {
            response.headers['access-control-allow-methods'] = request.headers['access-control-request-method'];
          }
        }
      }

      return h.continue;
    });
    // end COPY PASTE FROM HTTP SERVER CODE

    this.wsServer.register(hapiPluginWebsocket).catch(err => err && this.serverlessLog(err));

    const doAction = (ws, connectionId, name, event, doDefaultAction) => {
      const sendError = err => {
        if (ws.readyState === /* OPEN */1) {
          ws.send(JSON.stringify({ message:'Internal server error', connectionId, requestId:'1234567890' }));
        }

        debugLog(`Error in handler of action ${action}`, err);
      };

      let action = this.actions[name];

      if (!action && doDefaultAction) action = this.actions.$default;
      if (!action) return;

      function cb(err) {
        if (!err) return;
        sendError(err);
      }

      // TEMP
      const func = {
        ...action.fun,
        name,
      };
      const context = createLambdaContext(func, this.service.provider, cb);

      let p = null;

      try {
        p = action.handler(event, context, cb);
      }
      catch (err) {
        sendError(err);
      }

      if (p) {
        p.catch(err => {
          sendError(err);
        });
      }
    };

    this.wsServer.route({
      method: 'POST',
      path: '/',
      config: {
        payload: {
          output: 'data',
          parse: true,
          allow: 'application/json',
        },
        plugins: {
          websocket: {
            only: true,
            initially: false,
            connect: ({ ws, req }) => {
              const queryStringParameters = parseQuery(req.url);
              const connection = { connectionId:getUniqueId(), connectionTime:Date.now() };

              debugLog(`connect:${connection.connectionId}`);

              this.clients.set(ws, connection);

              let event = wsHelpers.createConnectEvent('$connect', 'CONNECT', connection, this.options);

              if (Object.keys(queryStringParameters).length > 0) {
                event = { queryStringParameters, ...event };
              }

              doAction(ws, connection.connectionId, '$connect', event, false);
            },
            disconnect: ({ ws }) => {
              const connection = this.clients.get(ws);

              debugLog(`disconnect:${connection.connectionId}`);

              this.clients.delete(ws);

              const event = wsHelpers.createDisconnectEvent('$disconnect', 'DISCONNECT', connection, this.options);

              doAction(ws, connection.connectionId, '$disconnect', event, false);
            },
          },
        },
      },
      handler: (request, h) => {
        const { initially, ws } = request.websocket();

        if (!request.payload || initially) {
          return h.response().code(204);
        }

        const connection = this.clients.get(ws);
        let actionName = null;

        if (this.websocketsApiRouteSelectionExpression.startsWith('$request.body.')) {
          actionName = request.payload;

          if (typeof actionName === 'object') {
            this.websocketsApiRouteSelectionExpression
            .replace('$request.body.', '')
            .split('.')
            .forEach(key => {
              if (actionName) {
                actionName = actionName[key];
              }
            });
          }
          else actionName = null;
        }

        if (typeof actionName !== 'string') {
          actionName = null;
        }

        const action = actionName || '$default';

        debugLog(`action:${action} on connection=${connection.connectionId}`);

        const event = wsHelpers.createEvent(action, 'MESSAGE', connection, request.payload, this.options);

        doAction(ws, connection.connectionId, action, event, true);

        return h.response().code(204);
      },
    });

    this.wsServer.route({
      method: 'GET',
      path: '/{path*}',
      handler: (request, h) => h.response().code(426),
    });

    this.wsServer.route({
      method: 'POST',
      path: '/@connections/{connectionId}',
      config: { payload: { parse: false } },
      handler: (request, h) => {
        debugLog(`got POST to ${request.url}`);

        const getByConnectionId = (map, searchValue) => {
          for (const [key, connection] of map.entries()) {
            if (connection.connectionId === searchValue) return key;
          }

          return undefined;
        };

        const ws = getByConnectionId(this.clients, request.params.connectionId);

        if (!ws) return h.response().code(410);
        if (!request.payload) return '';

        ws.send(request.payload.toString());

        debugLog(`sent data to connection:${request.params.connectionId}`);

        return '';
      },
    });

    this.wsServer.route({
      method: 'DELETE',
      path: '/@connections/{connectionId}',
      config: { payload: { parse: false } },
      handler: (request, h) => {
        debugLog(`got DELETE to ${request.url}`);

        const getByConnectionId = (map, searchValue) => {
          for (const [key, connection] of map.entries()) {
            if (connection.connectionId === searchValue) return key;
          }

          return undefined;
        };

        const ws = getByConnectionId(this.clients, request.params.connectionId);

        if (!ws) return h.response().code(410);

        ws.close();

        debugLog(`closed connection:${request.params.connectionId}`);

        return '';
      },
    });
  }

  _createWsAction(fun, funName, servicePath, funOptions, event) {
    let handler; // The lambda function
    Object.assign(process.env, this.originalEnvironment);

    try {
      if (this.options.noEnvironment) {
        // This evict errors in server when we use aws services like ssm
        const baseEnvironment = {
          AWS_REGION: 'dev',
        };

        if (!process.env.AWS_PROFILE) {
          baseEnvironment.AWS_ACCESS_KEY_ID = 'dev';
          baseEnvironment.AWS_SECRET_ACCESS_KEY = 'dev';
        }

        process.env = Object.assign(baseEnvironment, process.env);
      }
      else {
        Object.assign(
          process.env,
          { AWS_REGION: this.service.provider.region },
          this.service.provider.environment,
          this.service.functions[funName].environment
        );
      }

      process.env._HANDLER = fun.handler;
      handler = functionHelper.createHandler(funOptions, this.options);
    }
    catch (error) {
      return this.serverlessLog(`Error while loading ${funName}`, error);
    }

    const actionName = event.websocket.route;
    const action = { funName, fun, funOptions, servicePath, handler };

    this.actions[actionName] = action;
    this.serverlessLog(`Action '${event.websocket.route}'`);
  }

  _extractAuthFunctionName(endpoint) {
    const result = authFunctionNameExtractor(endpoint, this.serverlessLog);

    return result.unsupportedAuth ? null : result.authorizerName;
  }

  // All done, we can listen to incomming requests
  async _listen() {
    try {
      await this.wsServer.start();
    }
    catch (error) {
      console.error(`Unexpected error while starting serverless-offline websocket server on port ${this.options.websocketPort}:`, error);
      process.exit(1);
    }

    this.printBlankLine();
    this.serverlessLog(`Offline [websocket] listening on ws${this.options.httpsProtocol ? 's' : ''}://${this.options.host}:${this.options.websocketPort}`);
  }
};

function parseQuery(queryString) {
  const query = {};
  const parts = queryString.split('?');

  if (parts.length < 2) return {};

  const pairs = parts[1].split('&');

  pairs.forEach(pair => {
    const kv = pair.split('=');
    query[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || '');
  });

  return query;
}
<|endoftext|>


<commit_before>import React from 'react';
import lodash from 'lodash';

import refractToComponentsMap from 'refractToComponentMap';

import theme from 'theme';

class Attribute extends React.Component {
  static propTypes = {
    theme: React.PropTypes.object,
    element: React.PropTypes.object,
    expandableCollapsible: React.PropTypes.bool,
    parentElement: React.PropTypes.object,
  }

  static childContextTypes = {
    theme: React.PropTypes.object,
  }

  constructor(props) {
    super(props);
  }

  getChildContext() {
    return {
      theme: lodash.merge(this.props.theme || {}, theme),
    };
  }

  render() {
    if (!this.props.element) {
      return false;
    }

    const reactComponent = refractToComponentsMap[this.props.element.element];

    if (typeof reactComponent === 'undefined') {
      return new Error(`Unable to find a rendering component for ${this.props.element.element}`);
    }

    return React.createElement(reactComponent, {
      element: this.props.element,
      expandableCollapsible: this.props.expandableCollapsible,
      parentElement: this.props.parentElement,
    });
  }
}

export default Attribute;
<commit_msg>Fix applying of the custom theme object.<commit_after>import React from 'react';
import lodash from 'lodash';

import refractToComponentsMap from 'refractToComponentMap';

import theme from 'theme';

class Attribute extends React.Component {
  static propTypes = {
    theme: React.PropTypes.object,
    element: React.PropTypes.object,
    expandableCollapsible: React.PropTypes.bool,
    parentElement: React.PropTypes.object,
  }

  static childContextTypes = {
    theme: React.PropTypes.object,
  }

  constructor(props) {
    super(props);
  }

  getChildContext() {
    return {
      theme: lodash.merge(theme, this.props.theme || {}),
    };
  }

  render() {
    if (!this.props.element) {
      return false;
    }

    const reactComponent = refractToComponentsMap[this.props.element.element];

    if (typeof reactComponent === 'undefined') {
      return new Error(`Unable to find a rendering component for ${this.props.element.element}`);
    }

    return React.createElement(reactComponent, {
      element: this.props.element,
      expandableCollapsible: this.props.expandableCollapsible,
      parentElement: this.props.parentElement,
    });
  }
}

export default Attribute;
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/* global _playground */

define(function (require, exports, module) {
    "use strict";

    var EventEmitter = require("eventEmitter"),
        util = require("../util"),
        Promise = require("bluebird");

    /**
     * The Descriptor object provides helper methods for dealing with the
     * low-level native binding to Photoshop. This object will typically
     * not be used by user-level code.
     *
     * Emits low-level Photoshop events such as "select" with
     * the following parameters:
     *    1. @param {?} info about the event, dependent on event type (Note:
     *           this should become more specific as the native interface is
     *           further defined.)
     *
     * @extends EventEmitter
     * @constructor
     * @private
     */
    var Descriptor = function () {
        EventEmitter.call(this);
    };
    util.inherits(Descriptor, EventEmitter);

    /**
     * Event handler for events from the native bridge.
     *
     * @private
     * @param {*=} err Error information
     * @param {String} eventID typeID for event type
     * @param {Object} payload serialized ActionDescriptor for the event, dependent on event type
     */
    Descriptor.prototype._psEventHandler = function (err, eventID, payload) {
        if (err) {
            this.emit("error", "Failed to handle Photoshop event: " + err);
            return;
        }

        this.emit("all", eventID, payload);
        this.emit(eventID, payload);
    };

    /**
     * Emit the named event with the given arguments as parameters. Throws if the
     * event is "error" and there are no listeners.
     *
     * @override EventEmitter.prototype.emitEvent
     * @param {string|RegExp} event Name of the event to emit and execute listeners for
     * @param {Array=} args Optional array of arguments to be passed to each listener
     * @return {object} Current instance for chaining
     */
    Descriptor.prototype.emitEvent = function (event, args) {
        if (event === "error") {
            var listeners = this.getListeners(event);

            if (listeners.length === 0) {
                var message,
                    error;

                if (args.length > 0 && typeof args[0] === "string") {
                    message = args.shift();
                } else {
                    message = "Unhandled error event";
                }

                error = new Error(message);
                error.args = args;
                throw error;
            }
        }

        Descriptor.super_.prototype.emitEvent.call(this, event, args);
    };

    /**
     * Wraps certain type of parameters making it easier to call Descriptor.prototype.get
     * For arrays, returns the reference to array recursively mapping everything inside
     * For string values, references to the currently active one
     * For objects, leaves them as is.
     *
     * @private
     * @param {(string|Array.Object|Object)} toWrap object to reference to
     * @return {Object} Reference to the toWrap object in a form .get will accept
     */
    var _wrap = function (toWrap) {
        var reference;

        if (Array.isArray(toWrap)) {
            reference = {
                ref: toWrap.map(_wrap).reverse()
            };
        } else if (typeof toWrap === "string") {
            reference = {
                ref: toWrap,
                enum: "$Ordn",
                value: "$Trgt"
            };
        } else {
            reference = toWrap;
        }

        return reference;
    };

    /**
     * Executes a low-level "get" call using an ActionReference.
     *
     * @param {(string|Array.<(string|Object)>|Object)} reference The reference to retrieve. Can be:
     *     - string of a class name
     *     - Object representation of ActionReference key/value pairs
     *     - An array of a combination of the above, which will get turned into the appropriate ActionReference
     * @return {Promise.<?>} The value of the reference, dependent on reference type
     */
    Descriptor.prototype.get = function (reference) {
        var wrappedReference = _wrap(reference),
            getAsync = Promise.promisify(_playground.ps.descriptor.get,
                _playground.ps.descriptor);

        return getAsync(wrappedReference);
    };

    /**
     * Retrieves a property of a reference
     *
     * @param {String} reference The name of the reference
     * @param {String} property The name of the property
     * @return {Promise.<?>} The value of the property, dependent on reference type
     */
    Descriptor.prototype.getProperty = function (reference, property) {
        var propertyDescriptor = {
            ref: "$Prpr",
            property: property
        };

        return this.get([reference, propertyDescriptor])
            .then(function (obj) {
                if (!obj.hasOwnProperty(property)) {
                    throw new Error("No such property: " + property);
                }

                return obj[property];
            });
    };

    /**
     * Executes a low-level "play" call on the specified ActionDescriptor.
     *
     * @param {string} name Name of the ActionDescriptor command
     * @param {Object=} descriptor JS Object representation of ActionDescriptor key/value pairs, defaults to {}
     * @param {Object=} options options, defaults to "silent"
     * @return {Promise.<object>} Resolves when the call is complete (Note: eventually, this will
     *     return the value resulting from the execution of the ActionDescriptor, if any).
     */
    Descriptor.prototype.play = function (name, descriptor, options) {
        descriptor = descriptor || {};
        options = options || {
            interactionMode: _playground.ps.descriptor.interactionMode.SILENT
        };

        var playAsync = Promise.promisify(_playground.ps.descriptor.play,
            _playground.ps.descriptor);

        return playAsync(name, descriptor, options);
    };

    /**
     * Executes a low-level "play" call on the PlayObject by unwrapping it
     *
     * @param {PlayObject} playObject Contains command, descriptor and options information
     *
     * @returns {Promise} Resolves to the result of the call
     */
    Descriptor.prototype.playObject = function (playObject) {
        var command = playObject.command,
            descriptor = playObject.descriptor,
            options = playObject.options;

        return this.play(command, descriptor, options);
    };

    /**
     * Executes a low-level "batchPlay" call on the specified ActionDescriptors.
     *
     * @param {Array.<{name: string, descriptor: object}>} commands Array of ActionDescriptors to play
     * @param {object=} options Options applied to the execution of each ActionDescriptor individually
     * @param {{continueOnError: boolean=}}=} batchOptions Options that control how the batch of
     *      ActionDescriptors is executed.
     * @return {Promise.<Array.object>} Resolves with the list of ActionDescriptor results.
     */
    Descriptor.prototype.batchPlay = function (commands, options, batchOptions) {
        batchOptions = batchOptions || {};
        options = options || {
            interactionMode: _playground.ps.descriptor.interactionMode.SILENT
        };

        var batchPlayAsync = Promise.promisify(_playground.ps.descriptor.batchPlay,
            _playground.ps.descriptor);

        return batchPlayAsync(commands, options, batchOptions);
    };

    /**
     * Executes a low-level "batchPlay" call on the specified PlayObjects.
     *
     * @param {Array.<PlayObject>} objects Array of PlayObjects to play
     * @param {object=} options Options applied to the execution of each PlayObject individually
     * @param {{continueOnError: boolean}=} batchOptions Options that control how the batch of
     *      ActionDescriptors is executed.
     * @return {Promise.<Array.object>} Resolves with the list of ActionDescriptor results.
     */
    Descriptor.prototype.batchPlayObjects = function (objects, options, batchOptions) {
        batchOptions = batchOptions || {};
        options = options || {
            interactionMode: _playground.ps.descriptor.interactionMode.SILENT
        };

        var commands = objects.map(function (object) {
            return {
                name: object.command,
                descriptor: object.descriptor
            };
        });

        return this.batchPlay(commands, options, batchOptions);
    };

    /** @type {Descriptor} The Descriptor singleton */
    var theDescriptor = new Descriptor();

    // bind native phtooshop event handler to our handler function
    _playground.setNotifier(_playground.notifierGroup.PHOTOSHOP, {}, theDescriptor._psEventHandler.bind(theDescriptor));

    module.exports = theDescriptor;
});
<commit_msg>JSDoc touch up.<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/* global _playground */

define(function (require, exports, module) {
    "use strict";

    var EventEmitter = require("eventEmitter"),
        util = require("../util"),
        Promise = require("bluebird");

    /**
     * The Descriptor object provides helper methods for dealing with the
     * low-level native binding to Photoshop. This object will typically
     * not be used by user-level code.
     *
     * Emits low-level Photoshop events such as "select" with
     * the following parameters:
     *    1. @param {?} info about the event, dependent on event type (Note:
     *           this should become more specific as the native interface is
     *           further defined.)
     *
     * @extends EventEmitter
     * @constructor
     * @private
     */
    var Descriptor = function () {
        EventEmitter.call(this);
    };
    util.inherits(Descriptor, EventEmitter);

    /**
     * Event handler for events from the native bridge.
     *
     * @private
     * @param {*=} err Error information
     * @param {String} eventID typeID for event type
     * @param {Object} payload serialized ActionDescriptor for the event, dependent on event type
     */
    Descriptor.prototype._psEventHandler = function (err, eventID, payload) {
        if (err) {
            this.emit("error", "Failed to handle Photoshop event: " + err);
            return;
        }

        this.emit("all", eventID, payload);
        this.emit(eventID, payload);
    };

    /**
     * Emit the named event with the given arguments as parameters. Throws if the
     * event is "error" and there are no listeners.
     *
     * @override EventEmitter.prototype.emitEvent
     * @param {string|RegExp} event Name of the event to emit and execute listeners for
     * @param {Array=} args Optional array of arguments to be passed to each listener
     * @return {object} Current instance for chaining
     */
    Descriptor.prototype.emitEvent = function (event, args) {
        if (event === "error") {
            var listeners = this.getListeners(event);

            if (listeners.length === 0) {
                var message,
                    error;

                if (args.length > 0 && typeof args[0] === "string") {
                    message = args.shift();
                } else {
                    message = "Unhandled error event";
                }

                error = new Error(message);
                error.args = args;
                throw error;
            }
        }

        Descriptor.super_.prototype.emitEvent.call(this, event, args);
    };

    /**
     * Wraps certain type of parameters making it easier to call Descriptor.prototype.get
     * For arrays, returns the reference to array recursively mapping everything inside
     * For string values, references to the currently active one
     * For objects, leaves them as is.
     *
     * @private
     * @param {(string|Array.<object>|object)} toWrap object to reference to
     * @return {object} Reference to the toWrap object in a form .get will accept
     */
    var _wrap = function (toWrap) {
        var reference;

        if (Array.isArray(toWrap)) {
            reference = {
                ref: toWrap.map(_wrap).reverse()
            };
        } else if (typeof toWrap === "string") {
            reference = {
                ref: toWrap,
                enum: "$Ordn",
                value: "$Trgt"
            };
        } else {
            reference = toWrap;
        }

        return reference;
    };

    /**
     * Executes a low-level "get" call using an ActionReference.
     *
     * @param {(string|Array.<(string|Object)>|Object)} reference The reference to retrieve. Can be:
     *     - string of a class name
     *     - Object representation of ActionReference key/value pairs
     *     - An array of a combination of the above, which will get turned into the appropriate ActionReference
     * @return {Promise.<?>} The value of the reference, dependent on reference type
     */
    Descriptor.prototype.get = function (reference) {
        var wrappedReference = _wrap(reference),
            getAsync = Promise.promisify(_playground.ps.descriptor.get,
                _playground.ps.descriptor);

        return getAsync(wrappedReference);
    };

    /**
     * Retrieves a property of a reference
     *
     * @param {String} reference The name of the reference
     * @param {String} property The name of the property
     * @return {Promise.<?>} The value of the property, dependent on reference type
     */
    Descriptor.prototype.getProperty = function (reference, property) {
        var propertyDescriptor = {
            ref: "$Prpr",
            property: property
        };

        return this.get([reference, propertyDescriptor])
            .then(function (obj) {
                if (!obj.hasOwnProperty(property)) {
                    throw new Error("No such property: " + property);
                }

                return obj[property];
            });
    };

    /**
     * Executes a low-level "play" call on the specified ActionDescriptor.
     *
     * @param {string} name Name of the ActionDescriptor command
     * @param {Object=} descriptor JS Object representation of ActionDescriptor key/value pairs, defaults to {}
     * @param {Object=} options options, defaults to "silent"
     * @return {Promise.<object>} Resolves when the call is complete (Note: eventually, this will
     *     return the value resulting from the execution of the ActionDescriptor, if any).
     */
    Descriptor.prototype.play = function (name, descriptor, options) {
        descriptor = descriptor || {};
        options = options || {
            interactionMode: _playground.ps.descriptor.interactionMode.SILENT
        };

        var playAsync = Promise.promisify(_playground.ps.descriptor.play,
            _playground.ps.descriptor);

        return playAsync(name, descriptor, options);
    };

    /**
     * Executes a low-level "play" call on the PlayObject by unwrapping it
     *
     * @param {PlayObject} playObject Contains command, descriptor and options information
     *
     * @returns {Promise} Resolves to the result of the call
     */
    Descriptor.prototype.playObject = function (playObject) {
        var command = playObject.command,
            descriptor = playObject.descriptor,
            options = playObject.options;

        return this.play(command, descriptor, options);
    };

    /**
     * Executes a low-level "batchPlay" call on the specified ActionDescriptors.
     *
     * @param {Array.<{name: string, descriptor: object}>} commands Array of ActionDescriptors to play
     * @param {object=} options Options applied to the execution of each ActionDescriptor individually
     * @param {{continueOnError: boolean=}}=} batchOptions Options that control how the batch of
     *      ActionDescriptors is executed.
     * @return {Promise.<Array.object>} Resolves with the list of ActionDescriptor results.
     */
    Descriptor.prototype.batchPlay = function (commands, options, batchOptions) {
        batchOptions = batchOptions || {};
        options = options || {
            interactionMode: _playground.ps.descriptor.interactionMode.SILENT
        };

        var batchPlayAsync = Promise.promisify(_playground.ps.descriptor.batchPlay,
            _playground.ps.descriptor);

        return batchPlayAsync(commands, options, batchOptions);
    };

    /**
     * Executes a low-level "batchPlay" call on the specified PlayObjects.
     *
     * @param {Array.<PlayObject>} objects Array of PlayObjects to play
     * @param {object=} options Options applied to the execution of each PlayObject individually
     * @param {{continueOnError: boolean}=} batchOptions Options that control how the batch of
     *      ActionDescriptors is executed.
     * @return {Promise.<Array.object>} Resolves with the list of ActionDescriptor results.
     */
    Descriptor.prototype.batchPlayObjects = function (objects, options, batchOptions) {
        batchOptions = batchOptions || {};
        options = options || {
            interactionMode: _playground.ps.descriptor.interactionMode.SILENT
        };

        var commands = objects.map(function (object) {
            return {
                name: object.command,
                descriptor: object.descriptor
            };
        });

        return this.batchPlay(commands, options, batchOptions);
    };

    /** @type {Descriptor} The Descriptor singleton */
    var theDescriptor = new Descriptor();

    // bind native phtooshop event handler to our handler function
    _playground.setNotifier(_playground.notifierGroup.PHOTOSHOP, {}, theDescriptor._psEventHandler.bind(theDescriptor));

    module.exports = theDescriptor;
});
<|endoftext|>


<commit_before>/*jslint node: true, indent: 4 */
(function () {
    'use strict';
    var discourse,
        summons = {},
        configuration;

    /**
     * @var {string} description Brief description of this module for Help Docs
     */
    exports.description = 'Allow Summoning of bot to play in certain threads';

    /**
     * @var {object} configuration Default Configuration settings for this
     * sock_module
     */
    exports.configuration = {
        enabled: false,
        autoTimeout: 60 * 1000,
        userTimeout: 60 * 60 * 1000,
        probability: 1,
        messages: [
            '@%__username__% has summoned me, and so I appear.',
            'Yes master %__name__%, I shall appear as summoned.',
            'Yes mistress %__name__%, I shall appear as summoned.'
        ]
    };

    /**
     * @var {string} name The name of this sock_module
     */
    exports.name = 'Summoner';

    /**
     * @var {number} priority If defined by a sock_module it is the priority
     * of the module with respect to other modules.
     *
     * sock_modules **should not** define modules with negative permissions.
     * Default value is 50 with lower numbers being higher priority.
     */
    exports.priority = 1000;

    /**
     * @var {string} version The version of this sock_module
     */
    exports.version = '1.1.0';

    function purgeMemory() {
        var lastHour = (new Date().getTime()) - 60 * 60 * 1000, // an hour ago;
            k; //key
        for (k in summons) {
            if (summons.hasOwnProperty(k) && summons[k] < lastHour) {
                delete summons[k];
            }
        }
    }

    exports.onNotify = function onNotify(type, notification, topic,
        post, callback) {
        if (type === 'mentioned' && Math.random() < configuration.probability) {
            var now = (new Date().getTime()),
                r = Math.floor(Math.random() * configuration.messages.length),
                s = configuration.messages[r];
            if (summons[notification.topic_id] &&
                now < summons[notification.topic_id]) {
                return callback();
            }
            discourse.log(notification.data.display_username +
                ' summoned me to play in ' + notification.slug);
            s = s.replace(/%__(\w+)__%/g, function (m, key) {
                if (post.hasOwnProperty(key)) {
                    return post[key];
                }
                return m;
            });
            summons[notification.topic_id] = now + configuration.autoTimeout;
            discourse.createPost(notification.topic_id,
                notification.post_number, s,
                function () {
                    callback(true);
                });
        } else {
            callback();
        }
    };
    exports.begin = function begin(browser, config) {
        configuration = config.modules[exports.name];
        discourse = browser;
        setInterval(purgeMemory, 30 * 60 * 1000);
    };
}());
<commit_msg>Added category ignore to Summons module<commit_after>/*jslint node: true, indent: 4 */
(function () {
    'use strict';
    var discourse,
        summons = {},
        configuration;

    /**
     * @var {string} description Brief description of this module for Help Docs
     */
    exports.description = 'Allow Summoning of bot to play in certain threads';

    /**
     * @var {object} configuration Default Configuration settings for this
     * sock_module
     */
    exports.configuration = {
        enabled: false,
        autoTimeout: 60 * 1000,
        userTimeout: 60 * 60 * 1000,
        probability: 1,
        ignoreCategoryIds: [],
        messages: [
            '@%__username__% has summoned me, and so I appear.',
            'Yes master %__name__%, I shall appear as summoned.',
            'Yes mistress %__name__%, I shall appear as summoned.'
        ]
    };

    /**
     * @var {string} name The name of this sock_module
     */
    exports.name = 'Summoner';

    /**
     * @var {number} priority If defined by a sock_module it is the priority
     * of the module with respect to other modules.
     *
     * sock_modules **should not** define modules with negative permissions.
     * Default value is 50 with lower numbers being higher priority.
     */
    exports.priority = 1000;

    /**
     * @var {string} version The version of this sock_module
     */
    exports.version = '1.1.0';

    function purgeMemory() {
        var lastHour = (new Date().getTime()) - 60 * 60 * 1000, // an hour ago;
            k; //key
        for (k in summons) {
            if (summons.hasOwnProperty(k) && summons[k] < lastHour) {
                delete summons[k];
            }
        }
    }

    exports.onNotify = function onNotify(type, notification, topic,
        post, callback) {
        var ignoreCategory = configuration.ignoreCategoryIds.length > 0
            && configuration.ignoreCategoryIds.indexOf(topic.category_id) >= 0;
        if (!ignoreCategory && type === 'mentioned'
            && Math.random() < configuration.probability) {
            var now = (new Date().getTime()),
                r = Math.floor(Math.random() * configuration.messages.length),
                s = configuration.messages[r];
            if (summons[notification.topic_id] &&
                now < summons[notification.topic_id]) {
                return callback();
            }
            discourse.log(notification.data.display_username +
                ' summoned me to play in ' + notification.slug);
            s = s.replace(/%__(\w+)__%/g, function (m, key) {
                if (post.hasOwnProperty(key)) {
                    return post[key];
                }
                return m;
            });
            summons[notification.topic_id] = now + configuration.autoTimeout;
            discourse.createPost(notification.topic_id,
                notification.post_number, s,
                function () {
                    callback(true);
                });
        } else {
            callback();
        }
    };
    exports.begin = function begin(browser, config) {
        configuration = config.modules[exports.name];
        discourse = browser;
        setInterval(purgeMemory, 30 * 60 * 1000);
    };
}());
<|endoftext|>


<commit_before>/**
 * Static classes and properties made accessible
 * via the public Poker API.
 */
Poker.Deck = Deck
Poker.Hand = Hand
Poker.Player = Player
Poker.Pot = Pot
Poker.Table = Table
Poker.util = util

<commit_msg>make Dealer class publicly accessible through Poker static var<commit_after>/**
 * Static classes and properties made accessible
 * via the public Poker API.
 */
Poker.Deck = Deck
Poker.Hand = Hand
Poker.Player = Player
Poker.Pot = Pot
Poker.Table = Table
Poker.Dealer = Dealer
Poker.util = util

<|endoftext|>


<commit_before>
/**
 * Bullet chart generation
 */
pvc.BulletChart = pvc.Base.extend({

  bulletChartPanel : null,
  allowNoData: true,

  constructor: function(options){

    this.base(options);

    // Apply options
    pvc.mergeDefaults(this.options, pvc.BulletChart.defaultOptions, options);
  },

  preRender: function(){

    this.base();

    pvc.log("Prerendering in bulletChart");

    this.bulletChartPanel = new pvc.BulletChartPanel(this, {
      showValues: this.options.showValues,
      showTooltips: this.options.showTooltips,
      orientation: this.options.orientation
    });

    this.bulletChartPanel.appendTo(this.basePanel); // Add it
  }
}, {
  defaultOptions: {
      showValues: true,
      orientation: "horizontal",
      showTooltips: true,
      legend: false,

      bulletSize:     30,  // Bullet size
      bulletSpacing:  50,  // Spacing between bullets
      bulletMargin:  100,  // Left margin

      // Defaults
      bulletMarkers:  null,     // Array of markers to appear
      bulletMeasures: null,     // Array of measures
      bulletRanges:   null,     // Ranges
      bulletTitle:    "Bullet", // Title
      bulletSubtitle: "",       // Subtitle

      axisDoubleClickAction: null,

      crosstabMode: true,
      seriesInRows: true,

      tipsySettings: {
        gravity: "s",
        fade: true
      }
    }
});

/*
 * Bullet chart panel. Generates a bar chart. Specific options are:
 * orientation - horizontal or vertical. Default: vertical
 * showValues - Show or hide bar value. Default: false
 *
 * Has the following protovis extension points:
 *
 * chart_ - for the main chart Panel
 * bulletsPanel_ - for the bullets panel
 * bulletPanel_ - for the bullets pv.Layout.Bullet
 * bulletRange_ - for the bullet range
 * bulletMeasure_ - for the bullet measure
 * bulletMarker_ - for the marker
 * bulletRule_ - for the axis rule
 * bulletRuleLabel_ - for the axis rule label
 * bulletTitle_ - for the bullet title
 * bulletSubtitle_ - for the main bar label
 */

pvc.BulletChartPanel = pvc.BasePanel.extend({

  _parent: null,
  pvBullets: null,
  pvBullet: null,
  data: null,
  onSelectionChange: null,
  showTooltips: true,
  showValues: true,
  tipsySettings: {
    gravity: "s",
    fade: true
  },

//  constructor: function(chart, options){
//    this.base(chart,options);
//  },

  create: function(){

    var myself  = this;
    this.width  = this._parent.width;
    this.height = this._parent.height;

    var data = this.buildData();

    this.pvPanel = this._parent.getPvPanel().add(pv.Panel)
    .width(this.width)
    .height(this.height);

    var anchor = myself.chart.options.orientation=="horizontal"?"left":"bottom";
    var size, angle, align, titleOffset, ruleAnchor, leftPos, topPos;

    if(myself.chart.options.orientation=="horizontal"){
      size = this.width - this.chart.options.bulletMargin - 20;
      angle=0;
      align = "right";
      titleOffset = 0;
      ruleAnchor = "bottom";
      leftPos = this.chart.options.bulletMargin;
      topPos = function(){
        //TODO: 10
        return (this.index * (myself.chart.options.bulletSize + myself.chart.options.bulletSpacing));
      };
    }
    else
    {
      size = this.height - this.chart.options.bulletMargin - 20;
      angle = -Math.PI/2;
      align = "left";
      titleOffset = -12;
      ruleAnchor = "right";
      leftPos = function(){
        return myself.chart.options.bulletMargin + this.index * (myself.chart.options.bulletSize + myself.chart.options.bulletSpacing);
      };
      topPos = undefined;

    }

    this.pvBullets = this.pvPanel.add(pv.Panel)
    .data(data)
    [pvc.BasePanel.orthogonalLength[anchor]](size)
    [pvc.BasePanel.parallelLength[anchor]](this.chart.options.bulletSize)
    .margin(20)
    .left(leftPos) // titles will be on left always
    .top(topPos);

    this.pvBullet = this.pvBullets.add(pv.Layout.Bullet)
    .orient(anchor)
    .ranges(function(d){
      return d.ranges;
    })
    .measures(function(d){
      return d.measures;
    })
    .markers(function(d){
      return d.markers;
    });

    if (myself.chart.options.clickable){
      this.pvBullet
      .cursor("pointer")
      .event("click",function(d){
        var s = d.title;
        var c = d.subtitle;
        var ev = pv.event;
        return myself.chart.options.clickAction(s,c, d.measures, ev);
      });
    }

    this.pvBulletRange = this.pvBullet.range.add(pv.Bar);
    this.pvBulletMeasure = this.pvBullet.measure.add(pv.Bar)
    .text(function(d){
      return myself.chart.options.valueFormat(d);
    });

    this.pvBulletMarker = this.pvBullet.marker.add(pv.Dot)
    .shape("square")
    .fillStyle("white")
    .text(function(d){
      return myself.chart.options.valueFormat(d);
    });

    if(this.showTooltips){
      // Extend default
      this.extend(this.tipsySettings,"tooltip_");
      this.pvBulletMeasure.event("mouseover", pv.Behavior.tipsy(this.tipsySettings));
      this.pvBulletMarker.event("mouseover", pv.Behavior.tipsy(this.tipsySettings));
    }

    this.pvBulletRule = this.pvBullet.tick.add(pv.Rule);

    this.pvBulletRuleLabel = this.pvBulletRule.anchor(ruleAnchor).add(pv.Label)
    .text(this.pvBullet.x.tickFormat);

    this.pvBulletTitle = this.pvBullet.anchor(anchor).add(pv.Label)
    .font("bold 12px sans-serif")
    .textAngle(angle)
    .left(-10)
    .textAlign(align)
    .textBaseline("bottom")
    .left(titleOffset)
    .text(function(d){
      return d.title;
    });

    this.pvBulletSubtitle = this.pvBullet.anchor(anchor).add(pv.Label)
    .textStyle("#666")
    .textAngle(angle)
    .textAlign(align)
    .textBaseline("top")
    .left(titleOffset)
    .text(function(d){
      return d.subtitle;
    });

	var doubleClickAction = (typeof(myself.chart.options.axisDoubleClickAction) == 'function') ?
	function(d, e) {
		ignoreClicks = 2;
		myself.chart.options.axisDoubleClickAction(d, e);

	}: null;

    if (doubleClickAction) {
    	this.pvBulletTitle.events('all')  //labels don't have events by default
            .event("dblclick", function(d){
                    doubleClickAction(d, arguments[arguments.length-1]);
                });

    	this.pvBulletSubtitle.events('all')  //labels don't have events by default
            .event("dblclick", function(d){
                    doubleClickAction(d, arguments[arguments.length-1]);
                });

    }

    // Extension points
    this.extend(this.pvBullets,"bulletsPanel_");
    this.extend(this.pvBullet,"bulletPanel_");
    this.extend(this.pvBulletRange,"bulletRange_");
    this.extend(this.pvBulletMeasure,"bulletMeasure_");
    this.extend(this.pvBulletMarker,"bulletMarker_");
    this.extend(this.pvBulletRule,"bulletRule_");
    this.extend(this.pvBulletRuleLabel,"bulletRuleLabel_");
    this.extend(this.pvBulletTitle,"bulletTitle_");
    this.extend(this.pvBulletSubtitle,"bulletSubtitle_");

    // Extend body
    this.extend(this.pvPanel,"chart_");
  },

  /*
   * Data array to back up bullet charts; Case 1:
   *
   * 1) No data is passed - In this case, we'll grab all the value from the options
   * and generate only one bullet
   *
   */

  buildData: function(){

    pvc.log("In buildData: " + this.chart.dataEngine.getInfo() );

    var defaultData = {
      title:     this.chart.options.bulletTitle,
      subtitle:  this.chart.options.bulletSubtitle,
      ranges:    this.chart.options.bulletRanges   || [],
      measures:  this.chart.options.bulletMeasures || [],
      markers:   this.chart.options.bulletMarkers  || []
    };

    var data = [];

    if(this.chart.dataEngine.getSeriesSize() == 0 ) {
      // No data
      data.push($.extend({}, defaultData));
    }
    else {
      // We have data. Iterate through the series.
      var indices = this.chart.dataEngine.getVisibleSeriesIndexes();
      for(var i in indices) if (indices.hasOwnProperty(i))
      {
        var s = this.chart.dataEngine.getSerieByIndex(i);
        var v = this.chart.dataEngine.getVisibleValuesForSeriesIndex(i);
        var d = $.extend({},defaultData);

        switch(v.length){
          case 0:
            // Value only
            d.measures = [s];
            break;
          case 2:
            // Name, value and markers
            d.markers = [v[1]];
          case 1:
            // name and value
            d.title = s;
            d.measures = [v[0]];
            break;
          default:
            // greater or equal 4
            d.title = s;
            d.subtitle = v[0];
            d.measures = [v[1]];
            d.markers = [v[2]];
            if (v.length >= 3){
              d.ranges = v.slice(3);
            }
        }
        data.push(d);
      }
    }

    return data;
  }
});<commit_msg>* Added support for value and label distinction, for title and subtitle, on the bullet chart.<commit_after>
/**
 * Bullet chart generation
 */
pvc.BulletChart = pvc.Base.extend({

  bulletChartPanel : null,
  allowNoData: true,

  constructor: function(options){

    this.base(options);

    // Apply options
    pvc.mergeDefaults(this.options, pvc.BulletChart.defaultOptions, options);
  },

  preRender: function(){

    this.base();

    pvc.log("Prerendering in bulletChart");

    this.bulletChartPanel = new pvc.BulletChartPanel(this, {
      showValues: this.options.showValues,
      showTooltips: this.options.showTooltips,
      orientation: this.options.orientation
    });

    this.bulletChartPanel.appendTo(this.basePanel); // Add it
  }
}, {
  defaultOptions: {
      showValues: true,
      orientation: "horizontal",
      showTooltips: true,
      legend: false,

      bulletSize:     30,  // Bullet size
      bulletSpacing:  50,  // Spacing between bullets
      bulletMargin:  100,  // Left margin

      // Defaults
      bulletMarkers:  null,     // Array of markers to appear
      bulletMeasures: null,     // Array of measures
      bulletRanges:   null,     // Ranges
      bulletTitle:    "Bullet", // Title
      bulletSubtitle: "",       // Subtitle

      axisDoubleClickAction: null,

      crosstabMode: true,
      seriesInRows: true,

      tipsySettings: {
        gravity: "s",
        fade: true
      }
    }
});

/*
 * Bullet chart panel. Generates a bar chart. Specific options are:
 * orientation - horizontal or vertical. Default: vertical
 * showValues - Show or hide bar value. Default: false
 *
 * Has the following protovis extension points:
 *
 * chart_ - for the main chart Panel
 * bulletsPanel_ - for the bullets panel
 * bulletPanel_ - for the bullets pv.Layout.Bullet
 * bulletRange_ - for the bullet range
 * bulletMeasure_ - for the bullet measure
 * bulletMarker_ - for the marker
 * bulletRule_ - for the axis rule
 * bulletRuleLabel_ - for the axis rule label
 * bulletTitle_ - for the bullet title
 * bulletSubtitle_ - for the main bar label
 */

pvc.BulletChartPanel = pvc.BasePanel.extend({

  _parent: null,
  pvBullets: null,
  pvBullet: null,
  data: null,
  onSelectionChange: null,
  showTooltips: true,
  showValues: true,
  tipsySettings: {
    gravity: "s",
    fade: true
  },

//  constructor: function(chart, options){
//    this.base(chart,options);
//  },

  create: function(){

    var myself  = this;
    this.width  = this._parent.width;
    this.height = this._parent.height;

    var data = this.buildData();

    this.pvPanel = this._parent.getPvPanel().add(pv.Panel)
    .width(this.width)
    .height(this.height);

    var anchor = myself.chart.options.orientation=="horizontal"?"left":"bottom";
    var size, angle, align, titleOffset, ruleAnchor, leftPos, topPos;

    if(myself.chart.options.orientation=="horizontal"){
      size = this.width - this.chart.options.bulletMargin - 20;
      angle=0;
      align = "right";
      titleOffset = 0;
      ruleAnchor = "bottom";
      leftPos = this.chart.options.bulletMargin;
      topPos = function(){
        //TODO: 10
        return (this.index * (myself.chart.options.bulletSize + myself.chart.options.bulletSpacing));
      };
    }
    else
    {
      size = this.height - this.chart.options.bulletMargin - 20;
      angle = -Math.PI/2;
      align = "left";
      titleOffset = -12;
      ruleAnchor = "right";
      leftPos = function(){
        return myself.chart.options.bulletMargin + this.index * (myself.chart.options.bulletSize + myself.chart.options.bulletSpacing);
      };
      topPos = undefined;

    }

    this.pvBullets = this.pvPanel.add(pv.Panel)
    .data(data)
    [pvc.BasePanel.orthogonalLength[anchor]](size)
    [pvc.BasePanel.parallelLength[anchor]](this.chart.options.bulletSize)
    .margin(20)
    .left(leftPos) // titles will be on left always
    .top(topPos);

    this.pvBullet = this.pvBullets.add(pv.Layout.Bullet)
    .orient(anchor)
    .ranges(function(d){
      return d.ranges;
    })
    .measures(function(d){
      return d.measures;
    })
    .markers(function(d){
      return d.markers;
    });

    if (myself.chart.options.clickable){
      this.pvBullet
      .cursor("pointer")
      .event("click",function(d){
        var s = d.title;
        var c = d.subtitle;
        var ev = pv.event;
        return myself.chart.options.clickAction(s,c, d.measures, ev);
      });
    }

    this.pvBulletRange = this.pvBullet.range.add(pv.Bar);
    this.pvBulletMeasure = this.pvBullet.measure.add(pv.Bar)
    .text(function(d){
      return myself.chart.options.valueFormat(d);
    });

    this.pvBulletMarker = this.pvBullet.marker.add(pv.Dot)
    .shape("square")
    .fillStyle("white")
    .text(function(d){
      return myself.chart.options.valueFormat(d);
    });

    if(this.showTooltips){
      // Extend default
      this.extend(this.tipsySettings,"tooltip_");
      this.pvBulletMeasure.event("mouseover", pv.Behavior.tipsy(this.tipsySettings));
      this.pvBulletMarker.event("mouseover", pv.Behavior.tipsy(this.tipsySettings));
    }

    this.pvBulletRule = this.pvBullet.tick.add(pv.Rule);

    this.pvBulletRuleLabel = this.pvBulletRule.anchor(ruleAnchor).add(pv.Label)
    .text(this.pvBullet.x.tickFormat);

    this.pvBulletTitle = this.pvBullet.anchor(anchor).add(pv.Label)
    .font("bold 12px sans-serif")
    .textAngle(angle)
    .left(-10)
    .textAlign(align)
    .textBaseline("bottom")
    .left(titleOffset)
    .text(function(d){
      return d.formattedTitle;
    });

    this.pvBulletSubtitle = this.pvBullet.anchor(anchor).add(pv.Label)
    .textStyle("#666")
    .textAngle(angle)
    .textAlign(align)
    .textBaseline("top")
    .left(titleOffset)
    .text(function(d){
      return d.formattedSubtitle;
    });

    var doubleClickAction = (typeof(myself.chart.options.axisDoubleClickAction) == 'function') ?
    function(d, e) {
            //ignoreClicks = 2;
            myself.chart.options.axisDoubleClickAction(d, e);

    }: null;

    if (doubleClickAction) {
    	this.pvBulletTitle.events('all')  //labels don't have events by default
            .event("dblclick", function(d){
                    doubleClickAction(d, arguments[arguments.length-1]);
                });

    	this.pvBulletSubtitle.events('all')  //labels don't have events by default
            .event("dblclick", function(d){
                    doubleClickAction(d, arguments[arguments.length-1]);
                });

    }

    // Extension points
    this.extend(this.pvBullets,"bulletsPanel_");
    this.extend(this.pvBullet,"bulletPanel_");
    this.extend(this.pvBulletRange,"bulletRange_");
    this.extend(this.pvBulletMeasure,"bulletMeasure_");
    this.extend(this.pvBulletMarker,"bulletMarker_");
    this.extend(this.pvBulletRule,"bulletRule_");
    this.extend(this.pvBulletRuleLabel,"bulletRuleLabel_");
    this.extend(this.pvBulletTitle,"bulletTitle_");
    this.extend(this.pvBulletSubtitle,"bulletSubtitle_");

    // Extend body
    this.extend(this.pvPanel,"chart_");
  },

  /*
   * Data array to back up bullet charts; Case 1:
   *
   * 1) No data is passed - In this case, we'll grab all the value from the options
   * and generate only one bullet
   *
   */

  buildData: function(){

    pvc.log("In buildData: " + this.chart.dataEngine.getInfo() );

    var defaultData = {
      title:     this.chart.options.bulletTitle,
      subtitle:  this.chart.options.bulletSubtitle,
      ranges:    this.chart.options.bulletRanges   || [],
      measures:  this.chart.options.bulletMeasures || [],
      markers:   this.chart.options.bulletMarkers  || []
    };

    var data = [],
        options = this.chart.options,
        getSeriesLabel   = options.getSeriesLabel || pv.identity,
        getCategoryLabel = options.getCategoryLabel || pv.identity;

    if(this.chart.dataEngine.getSeriesSize() == 0 ) {
      // No data
      data.push($.extend({}, defaultData));
    }
    else {
      // We have data. Iterate through the series.
      var indices = this.chart.dataEngine.getVisibleSeriesIndexes();
      for(var i in indices) if (indices.hasOwnProperty(i))
      {
        var s = this.chart.dataEngine.getSerieByIndex(i);
        var v = this.chart.dataEngine.getVisibleValuesForSeriesIndex(i);
        var d = $.extend({},defaultData);

        switch(v.length){
          case 0:
            // Value only
            d.measures = [s];
            break;

          case 2:
            // Name, value and markers
            d.markers = [v[1]];
            // NO break!
          case 1:
            // name and value
            d.title = s;
            d.formattedTitle = getCategoryLabel(s);
            d.measures = [v[0]];
            break;

          default:
            // greater or equal 4
            d.title = s;
            d.subtitle = v[0];
            d.formattedTitle = getCategoryLabel(s);
            d.formattedSubtitle = getSeriesLabel(v[0])
            d.measures = [v[1]];
            d.markers = [v[2]];
            if (v.length >= 3){
              d.ranges = v.slice(3);
            }
        }
        data.push(d);
      }
    }

    return data;
  }
});<|endoftext|>


<commit_before>(function (quartett, undefined) {
    "use strict";
    // Game API

    quartett.Game = function Game(options){
        var that = this;
            that._cardStack = new quartett.Cardstack();
            that._playerStack = {};
            that._playerList = [];
            that._playerCount = 0;

        var assertConfig = function(config){
            if (!config){
                throw new Error("options are mandatory")
            }

            //Todo: Let's make that more sophisticated. If the user provides an object instead of strings, extend
            //the final player by each property of the object that the user provided.
            if (!config.player){
                throw new Error('options.player must be an array of player names')
            }

            if (!config.cards){
                throw new Error('options.cards can either be an array of cards, an url that returns cards or a function that returns cards');
            }

            config.cardComparer = config.cardComparer || quartett.DefaultCardComparer;
        };

        var createPlayer = function(player){
            player.forEach(function(value){

                var tempPlayer = new quartett.Player({name: value, game: that });

                that._playerStack[value] = tempPlayer;
                that._playerList.push(tempPlayer);

                that._giveTopmostCardsToPlayer(tempPlayer, that._initialCardsPerPlayer)

                that['get' + value] = function(){
                        var player = that._playerStack[value];
                        if (!player){
                            throw new Error('Player ' + player + ' does not exist');
                        }
                        return player;
                }
            });
        };

        assertConfig(options);
        //Todo handle transformation of card types. For now let's assume we get an array of cards
        that._cardStack.add(options.cards);
        that._gameCardCount = options.cards.length;

        that._playerCount = options.player.length;
        that._initialCardsPerPlayer = that._cardStack.getLength() / that._playerCount;

        if (quartett.Util.isFunction(options.beforePlayerInitialized)){
            options.beforePlayerInitialized(this);
        }

        createPlayer(options.player);

        that._cardComparer = new options.cardComparer();
        that._options = options;
    };

    //PUBLIC METHODS

    quartett.Game.prototype.start = function(playerName){
        this._activePlayer = playerName ? this._playerStack[playerName] : this._playerList[0];
    };

    quartett.Game.prototype.playCard = function(property){

        //get an array of objects with all other players and their top card
        //give all cards to the player with the best score on that property
        //if it's a draw do nothing and let the active player play another property

        var that = this;

        that._raiseErrorIfFinished();

        //get the comparer for the property we currently play on
        var comparer = that._cardComparer.getSortFuncForProperty(property);

        var unwrapAndCompare = function(a, b){
            return comparer(a.topCard[property], b.topCard[property]);
        };

        var otherPlayer = that._getInactivePlayerAndTheirTopmostCards()
                              .sort(unwrapAndCompare);

        var best = otherPlayer[otherPlayer.length - 1];

        var scoreAgainstTheBest = comparer(that._activePlayer.getTopmostCard()[property], best.topCard[property]);

        var giveTopmostCardsToPlayer = function(player){
            var cards = that._getAllTopMostCards();
            player.add(cards);
        };

        //Todo
        //handle the fact that there can be draws not only between the active player and the best other player
        //but also between the other players. E.g. the active player has a score of 3 on the property but two
        //other players both have a score of 4

        if (scoreAgainstTheBest === -1){

            //figure out if the best other player is the only winner or if there is a draw between other player going on
            if(otherPlayer.length > 1){
                var second = otherPlayer[otherPlayer.length - 2];
                var scoreAgainstSecond = comparer(best.topCard[property], second.topCard[property]);

                if (scoreAgainstSecond === 0){
                    //there's a draw between the best other player and at least the second best other player
                    //notify the draw, change the active player and let him play on another property

                    //We need to blacklist this card so that the active player can't win the pot on another property
                    //because he already lost on this property but the card continues beeing played
                    this._activePlayer.getTopmostCard()._blacklisted = true;

                    this._dispatchEvent("drawHappened", this);
                }
                else{
                    //there's no draw between the best and the second. Hand over the cards!
                    giveTopmostCardsToPlayer(best.player);
                }
            }
            else{
                //there is just on clear winner, hand the cards over to him/her
                giveTopmostCardsToPlayer(best.player);
            }

            //in any case, the active player changed
            this._activePlayer = best.player;
            //notify that the active user has changed
            this._dispatchEvent("activePlayerChanged", this);

        }
        else if (scoreAgainstTheBest === 0){
            //notify that we have a draw on this property. This basically means the active player
            //needs to play on another property
            this._dispatchEvent("drawHappened", this);
        }
        else{
            giveTopmostCardsToPlayer(this._activePlayer);
        }

        //notify progress of the game
        this._dispatchEvent("gameMoved", this);

        that._figureOutIfGameIsFinished();
    };

    quartett.Game.prototype.getActivePlayer = function(){
        return this._activePlayer;
    };

    quartett.Game.prototype.getCards = function(){
        return this._cardStack.getCards();
    };

    //PRIVATE METHODS

    quartett.Game.prototype._giveTopmostCardsToPlayer = function(player, nCards){
        nCards = !nCards ? 1 : nCards;

        while(nCards > 0){
            var topmostCard = this._cardStack.popOutTopmostCard();
            //remove the blacklist marker that might be on the card
            topmostCard._blacklisted = false;
            player.add(topmostCard);

            nCards--;
        }
    };

    quartett.Game.prototype._getInactivePlayerAndTheirTopmostCards = function(){
        var that = this;
        return that._playerList
            .filter(function(player){
                return player !== that._activePlayer && !player.getTopmostCard()._blacklisted;
            })
            .map(function(player){
                return {
                    player: player,
                    topCard: player.getTopmostCard()
                };
            });
    };

    quartett.Game.prototype._raiseErrorIfFinished = function(){
        if (this._finished) {
            throw new Error("Game already finished");
        }
    };

    quartett.Game.prototype._dispatchEvent = function(eventName){
        if (quartett.Util.isFunction(this._options[eventName])){
            var args = Array.prototype.slice.call(arguments);
            args.splice(0,1);
            this._options[eventName].apply(this, args);
        }
    };

    quartett.Game.prototype._getAllTopMostCards = function(){
        return this._playerList.map(function(player){
            return player.popOutTopmostCard();
        });
    };

    quartett.Game.prototype._figureOutIfGameIsFinished = function(){
        if(this._activePlayer.getCards().length === this._gameCardCount){
            this._finished = true;
            if (quartett.Util.isFunction(this._options.gameFinished)){
                this._dispatchEvent("gameFinished", this, {
                    winner: this._activePlayer
                });
            }
        }
    };

})(quartett);<commit_msg>improved comments<commit_after>(function (quartett, undefined) {
    "use strict";
    // Game API

    quartett.Game = function Game(options){
        var that = this;
            that._cardStack = new quartett.Cardstack();
            that._playerStack = {};
            that._playerList = [];
            that._playerCount = 0;

        var assertConfig = function(config){
            if (!config){
                throw new Error("options are mandatory")
            }

            //Todo: Let's make that more sophisticated. If the user provides an object instead of strings, extend
            //the final player by each property of the object that the user provided.
            if (!config.player){
                throw new Error('options.player must be an array of player names')
            }

            if (!config.cards){
                throw new Error('options.cards can either be an array of cards, an url that returns cards or a function that returns cards');
            }

            config.cardComparer = config.cardComparer || quartett.DefaultCardComparer;
        };

        var createPlayer = function(player){
            player.forEach(function(value){

                var tempPlayer = new quartett.Player({name: value, game: that });

                that._playerStack[value] = tempPlayer;
                that._playerList.push(tempPlayer);

                that._giveTopmostFreeCardsToPlayer(tempPlayer, that._initialCardsPerPlayer)

                that['get' + value] = function(){
                        var player = that._playerStack[value];
                        if (!player){
                            throw new Error('Player ' + player + ' does not exist');
                        }
                        return player;
                }
            });
        };

        assertConfig(options);
        //Todo handle transformation of card types. For now let's assume we get an array of cards
        that._cardStack.add(options.cards);
        that._gameCardCount = options.cards.length;

        that._playerCount = options.player.length;
        that._initialCardsPerPlayer = that._cardStack.getLength() / that._playerCount;

        if (quartett.Util.isFunction(options.beforePlayerInitialized)){
            options.beforePlayerInitialized(this);
        }

        createPlayer(options.player);

        that._cardComparer = new options.cardComparer();
        that._options = options;
    };

    //PUBLIC METHODS

    quartett.Game.prototype.start = function(playerName){
        this._activePlayer = playerName ? this._playerStack[playerName] : this._playerList[0];
    };

    quartett.Game.prototype.playCard = function(property){

        //get an array of objects with all other players and their top card
        //give all cards to the player with the best score on that property
        //if it's a draw do nothing and let the active player play another property

        var that = this;

        that._raiseErrorIfFinished();

        //get the comparer for the property we currently play on
        var comparer = that._cardComparer.getSortFuncForProperty(property);

        var unwrapAndCompare = function(a, b){
            return comparer(a.topCard[property], b.topCard[property]);
        };

        var otherPlayers = that._getInactivePlayerAndTheirTopmostCards()
                              .sort(unwrapAndCompare);

        var best = otherPlayers[otherPlayers.length - 1];

        var scoreAgainstTheBest = comparer(that._activePlayer.getTopmostCard()[property], best.topCard[property]);

        var giveTopmostCardsToPlayer = function(player){
            var cards = that._getAllTopMostCards();
            player.add(cards);
        };

        //Todo
        //handle the fact that there can be draws not only between the active player and the best other player
        //but also between the other players. E.g. the active player has a score of 3 on the property but two
        //other players both have a score of 4

        if (scoreAgainstTheBest === -1){

            //figure out if the best other player is the *only* winner or if there is a draw between other players going on
            if(otherPlayers.length > 1){
                var second = otherPlayers[otherPlayers.length - 2];
                var scoreAgainstSecond = comparer(best.topCard[property], second.topCard[property]);

                if (scoreAgainstSecond === 0){
                    //there's a draw between the best other player and at least the second best other player
                    //notify the draw, change the active player and let him play on another property

                    //We need to blacklist this card so that the active player can't win the pot on another property
                    //because he already lost on this property but the card continues beeing played
                    this._activePlayer.getTopmostCard()._blacklisted = true;

                    this._dispatchEvent("drawHappened", this);
                }
                else{
                    //there's no draw between the best and the second. Hand over the cards!
                    giveTopmostCardsToPlayer(best.player);
                }
            }
            else{
                //there is just on clear winner, hand the cards over to him/her
                giveTopmostCardsToPlayer(best.player);
            }

            //in any case, the active player changed
            this._activePlayer = best.player;
            //notify that the active user has changed
            this._dispatchEvent("activePlayerChanged", this);

        }
        else if (scoreAgainstTheBest === 0){
            //notify that we have a draw on this property. This basically means the active player
            //needs to play on another property
            this._dispatchEvent("drawHappened", this);
        }
        else{
            giveTopmostCardsToPlayer(this._activePlayer);
        }

        //notify progress of the game
        this._dispatchEvent("gameMoved", this);

        that._figureOutIfGameIsFinished();
    };

    quartett.Game.prototype.getActivePlayer = function(){
        return this._activePlayer;
    };

    quartett.Game.prototype.getCards = function(){
        return this._cardStack.getCards();
    };

    //PRIVATE METHODS

    quartett.Game.prototype._giveTopmostFreeCardsToPlayer = function(player, nCards){
        nCards = !nCards ? 1 : nCards;

        while(nCards > 0){
            var topmostCard = this._cardStack.popOutTopmostCard();
            //remove the blacklist marker that might be on the card
            topmostCard._blacklisted = false;
            player.add(topmostCard);

            nCards--;
        }
    };

    quartett.Game.prototype._getInactivePlayerAndTheirTopmostCards = function(){
        var that = this;
        return that._playerList
            .filter(function(player){
                return player !== that._activePlayer && !player.getTopmostCard()._blacklisted;
            })
            .map(function(player){
                return {
                    player: player,
                    topCard: player.getTopmostCard()
                };
            });
    };

    quartett.Game.prototype._raiseErrorIfFinished = function(){
        if (this._finished) {
            throw new Error("Game already finished");
        }
    };

    quartett.Game.prototype._dispatchEvent = function(eventName){
        if (quartett.Util.isFunction(this._options[eventName])){
            var args = Array.prototype.slice.call(arguments);
            args.splice(0,1);
            this._options[eventName].apply(this, args);
        }
    };

    quartett.Game.prototype._getAllTopMostCards = function(){
        return this._playerList.map(function(player){
            return player.popOutTopmostCard();
        });
    };

    quartett.Game.prototype._figureOutIfGameIsFinished = function(){
        if(this._activePlayer.getCards().length === this._gameCardCount){
            this._finished = true;
            if (quartett.Util.isFunction(this._options.gameFinished)){
                this._dispatchEvent("gameFinished", this, {
                    winner: this._activePlayer
                });
            }
        }
    };

})(quartett);<|endoftext|>