<commit_before>// Copyright (c) 2015 Daniel Wachsstock
// MIT license:
// 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.

(function($){

$.fn.repo = function (user, time){
	var repo = 'https://cdn.rawgit.com/'+user+'/',
		deferred = $.Deferred(),
		localName = '$.repo.'+user,
		sha = localStorage[localName];
	if (arguments.length == 1) time = 1000*60*60; // one hour

	if (sha && +localStorage[localName+'.time'] + time > +new Date){
		deferred.resolve(repo+sha);
	}else{
		$.get('https://api.github.com/repos/'+user+'/git/refs/heads/master').then(function(data){
			localStorage[localName] = repo+data.object.sha;
			localStorage[localName+'.time'] = +new Date;
			deferred.resolve(repo+data.object.sha);
		});
	};
	return deferred;
}

})(jQuery);
<commit_msg>bugfix<commit_after>// Copyright (c) 2015 Daniel Wachsstock
// MIT license:
// 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.

(function($){

$.repo = function (user, time){
	var repo = 'https://cdn.rawgit.com/'+user+'/',
		deferred = $.Deferred(),
		localName = '$.repo.'+user,
		sha = localStorage[localName];
	if (arguments.length == 1) time = 1000*60*60; // one hour

	if (sha && +localStorage[localName+'.time'] + time > +new Date){
		deferred.resolve(repo+sha);
	}else{
		$.get('https://api.github.com/repos/'+user+'/git/refs/heads/master').then(function(data){
			localStorage[localName] = repo+data.object.sha;
			localStorage[localName+'.time'] = +new Date;
			deferred.resolve(repo+data.object.sha);
		});
	};
	return deferred;
}

})(jQuery);
<|endoftext|>


<commit_before>/*
 * Copyright 2013 The Polymer Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

var cheerio = require('cheerio');
var cleancss = require('clean-css');
var fs = require('fs');
var path = require('path');
var uglify = require('uglify-js');
var url = require('url');

var constants = require('./constants.js');
var optparser = require('./optparser.js');
var pathresolver = require('./pathresolver');
var utils = require('./utils');
var setTextContent = utils.setTextContent;
var getTextContent = utils.getTextContent;

var read = {};
var options = {};

// validate options with boolean return
function setOptions(optHash, callback) {
  optparser.processOptions(optHash, function(err, o) {
    if (err) {
      return callback(err);
    }
    options = o;
    callback();
  });
}

function exclude(regexes, href) {
  return regexes.some(function(r) {
    return r.test(href);
  });
}

function excludeImport(href) {
  return exclude(options.excludes.imports, href);
}

function excludeScript(href) {
  return exclude(options.excludes.scripts, href);
}

function excludeStyle(href) {
  return exclude(options.excludes.styles, href);
}

function readFile(file) {
  var content = fs.readFileSync(file, 'utf8');
  return content.replace(/^\uFEFF/, '');
}

// inline relative linked stylesheets into ');
      // clone attributes
      styleDoc('style').attr(el.attr());
      // don't set href or rel on the ');
      // clone attributes
      styleDoc('style').attr(el.attr());
      // don't set href or rel on the


<commit_before>var $dwui = function() {
  this.ko.self = this;
}

$dwui.preloadForm = function(jsondata, opts) {
  opts = opts || {};
  opts.element = opts.element || '#dw_graph';
  opts.width = opts.width || $(opts.element)[0].clientWidth;
  opts.height = opts.height || $(opts.element)[0].clientHeight;
  //if(typeof this.form == 'undefined' || this.form == null) return;
  if(typeof jsondata == 'string') jsondata = JSON.parse(jsondata);
  if(!(jsondata instanceof Array)) return;
  /*jsondata.forEach(function(d) {
    d.toString = function nodeToString() {
      return this.name + ':' + this.needs.join(','); // + ':' + Date.now();
    }
  })*/
  var n = new $dwui();

  var graph = n.graph = new $depweb_graph(opts.element, opts.width, opts.height, $depweb.fromArray(jsondata));
  n.ko.nodes = ko.mapping.fromJS(graph.data.nodes); //ko.observableArray(jsondata);
  //n.ko.nodes = ;
  //n.ko.links = ko.mapping.fromJS(graph.data.links);
  return n;
};

$dwui.prototype.ko = {};
$dwui.prototype.form = null;
$dwui.prototype.removeNeed = function(node, need) {
  console.log(arguments);
  console.log("index:", node.needs.indexOf(need));
  var ind = node.needs.indexOf(need);
  if(ind != -1) {
    node.needs.splice(ind, 1);
    this.ko.nodes.valueHasMutated();
    this.graph.data.nodesByName[node.name()].needs.splice(ind, 1);
    this.graph.updateData(this.graph.data.nodesByName[node.name()]);
  }
};
$dwui.prototype.exportform = function() {
  ko.mapping.toJSON(this.ko.dw_gdata);
};
$dwui.prototype.refresh = function() {
  ko.applyBindings(this.ko);
};
<commit_msg>Serious changes...again<commit_after>var $dwui = function() {
  this.ko.self = this;
}

$dwui.preloadForm = function(jsondata, opts) {
  opts = opts || {};
  opts.element = opts.element || '#dw_graph';
  opts.width = opts.width || $(opts.element)[0].clientWidth;
  opts.height = opts.height || $(opts.element)[0].clientHeight;
  //if(typeof this.form == 'undefined' || this.form == null) return;
  if(typeof jsondata == 'string') jsondata = JSON.parse(jsondata);
  if(!(jsondata instanceof Array)) return;
  /*jsondata.forEach(function(d) {
    d.toString = function nodeToString() {
      return this.name + ':' + this.needs.join(','); // + ':' + Date.now();
    }
  })*/
  var n = new $dwui();

  var graph = n.graph = new $depweb_graph(opts.element, opts.width, opts.height, $depweb.fromArray(jsondata));
  n.ko.nodes = ko.mapping.fromJS(graph.data.nodes); //ko.observableArray(jsondata);
  //n.ko.nodes = ;
  //n.ko.links = ko.mapping.fromJS(graph.data.links);
  return n;
};

$dwui.prototype.ko = {};
$dwui.prototype.form = null;
$dwui.prototype.removeNeed = function(node, need) {
  var ind = node.needs.indexOf(need);
  if(ind != -1) {
    node.needs.splice(ind, 1);
    this.ko.nodes.valueHasMutated();
    this.graph.data.nodesByName[node.name()].needs.splice(ind, 1);
    this.graph.updateData(this.graph.data.nodesByName[node.name()]);
  }
};
$dwui.prototype.addNeed = function(n, d, e) {

  var el = $(e.target);
  if(e.keyCode == 13) {
    var newneed =  el.val();
    n.needs.push(newneed);
    this.graph.data.nodesByName[n.name()].needs.push(newneed);
    this.graph.updateData(this.graph.data.nodesByName[n.name()]);
    this.graph.redraw();
    el.val('');
    return;
  }
  if(!(/.+/).test(String.fromCharCode(e.keyCode))) { return; }
  var c = el.val();
  el.val(c + String.fromCharCode(e.charCode));
}
$dwui.prototype.addNode = function(d, e) {
  var el = $(e.target);
  if(e.keyCode == 13) {
    var newnode = new $depweb.node(el.val());
    this.graph.data.addNode(newnode);
    this.ko.nodes.push(ko.mapping.fromJS(newnode));
    el.val('');
    return;
  }
  if(!(/.+/).test(String.fromCharCode(e.keyCode))) { return; }

  var c = el.val();
  el.val(c + String.fromCharCode(e.charCode));
}
$dwui.prototype.exportform = function() {
  ko.mapping.toJSON(this.ko.dw_gdata);
};
$dwui.prototype.refresh = function() {
  ko.applyBindings(this.ko);
};
<|endoftext|>


<commit_before>/*
	jQuery Zoom v1.7.1 - 2013-03-12
	(c) 2013 Jack Moore - jacklmoore.com/zoom
	license: http://www.opensource.org/licenses/mit-license.php
*/
(function ($) {
	var defaults = {
		url: false,
		callback: false,
		target: false,
		duration: 120,
		on: 'mouseover' // other options: 'grab', 'click', 'toggle'
	};

	// Core Zoom Logic, independent of event listeners.
	$.zoom = function(target, source, img) {
		var outerWidth,
			outerHeight,
			xRatio,
			yRatio,
			offset,
			position = $(target).css('position');

		// The parent element needs positioning so that the zoomed element can be correctly positioned within.
		$(target).css({
			position: /(absolute|fixed)/.test() ? position : 'relative',
			overflow: 'hidden'
		});

		$(img)
			.addClass('zoomImg')
			.css({
				position: 'absolute',
				top: 0,
				left: 0,
				opacity: 0,
				width: img.width,
				height: img.height,
				border: 'none',
				maxWidth: 'none'
			})
			.appendTo(target);

		return {
			init: function() {
				outerWidth = $(target).outerWidth();
				outerHeight = $(target).outerHeight();
				xRatio = (img.width - outerWidth) / $(source).outerWidth();
				yRatio = (img.height - outerHeight) / $(source).outerHeight();
				offset = $(source).offset();
			},
			move: function (e) {
				var left = (e.pageX - offset.left),
					top = (e.pageY - offset.top);

				top = Math.max(Math.min(top, outerHeight), 0);
				left = Math.max(Math.min(left, outerWidth), 0);

				img.style.left = (left * -xRatio) + 'px';
				img.style.top = (top * -yRatio) + 'px';
			}
		};
	};

	$.fn.zoom = function (options) {
		return this.each(function () {
			var
			settings = $.extend({}, defaults, options || {}),
			//target will display the zoomed iamge
			target = settings.target || this,
			//source will provide zoom location info (thumbnail)
			source = this,
			img = new Image(),
			$img = $(img),
			mousemove = 'mousemove',
			clicked = false;

			// If a url wasn't specified, look for an image element.
			if (!settings.url) {
				settings.url = $(source).find('img').attr('src');
				if (!settings.url) {
					return;
				}
			}

			img.onload = function () {
				var zoom = $.zoom(target, source, img);

				function start(e) {
					zoom.init();
					zoom.move(e);

					// Skip the fade-in for IE8 and lower since it chokes on fading-in
					// and changing position based on mousemovement at the same time.
					$img.stop()
					.fadeTo($.support.opacity ? settings.duration : 0, 1);
				}

				function stop() {
					$img.stop()
					.fadeTo(settings.duration, 0);
				}

				if (settings.on === 'grab') {
					$(source).on('mousedown',
						function (e) {
							$(document).one('mouseup',
								function () {
									stop();

									$(document).off(mousemove, zoom.move);
								}
							);

							start(e);

							$(document).on(mousemove, zoom.move);

							e.preventDefault();
						}
					);
				} else if (settings.on === 'click') {
					$(source).on('click',
						function (e) {
							if (clicked) {
								// bubble the event up to the document to trigger the unbind.
								return;
							} else {
								clicked = true;
								start(e);
								$(document).on(mousemove, zoom.move);
								$(document).one('click',
									function () {
										stop();
										clicked = false;
										$(document).off(mousemove, zoom.move);
									}
								);
								return false;
							}
						}
					);
				} else if (settings.on === 'toggle') {
					$(source).on('click',
						function (e) {
							if (clicked) {
								stop();
							} else {
								start(e);
							}
							clicked = !clicked;
						}
					);
				} else {
					zoom.init(); // Pre-emptively call init because IE7 will fire the mousemove handler before the hover handler.

					$(source)
						.on('mouseenter', start)
						.on('mouseleave', stop)
						.on(mousemove, zoom.move);
				}

				if ($.isFunction(settings.callback)) {
					settings.callback.call(img);
				}
			};

			img.src = settings.url;
		});
	};

	$.fn.zoom.defaults = defaults;
}(window.jQuery));<commit_msg>reset zoomImg's size on reload<commit_after>/*
	jQuery Zoom v1.7.1 - 2013-03-12
	(c) 2013 Jack Moore - jacklmoore.com/zoom
	license: http://www.opensource.org/licenses/mit-license.php
*/
(function ($) {
	var defaults = {
		url: false,
		callback: false,
		target: false,
		duration: 120,
		on: 'mouseover' // other options: 'grab', 'click', 'toggle'
	};

	// Core Zoom Logic, independent of event listeners.
	$.zoom = function(target, source, img) {
		var outerWidth,
			outerHeight,
			xRatio,
			yRatio,
			offset,
			position = $(target).css('position');

		// The parent element needs positioning so that the zoomed element can be correctly positioned within.
		$(target).css({
			position: /(absolute|fixed)/.test() ? position : 'relative',
			overflow: 'hidden'
		});

		$(img)
			.addClass('zoomImg')
			.css({
				position: 'absolute',
				top: 0,
				left: 0,
				opacity: 0,
				width: img.width,
				height: img.height,
				border: 'none',
				maxWidth: 'none'
			})
			.appendTo(target);

		return {
			init: function() {
				outerWidth = $(target).outerWidth();
				outerHeight = $(target).outerHeight();
				xRatio = (img.width - outerWidth) / $(source).outerWidth();
				yRatio = (img.height - outerHeight) / $(source).outerHeight();
				offset = $(source).offset();
			},
			move: function (e) {
				var left = (e.pageX - offset.left),
					top = (e.pageY - offset.top);

				top = Math.max(Math.min(top, outerHeight), 0);
				left = Math.max(Math.min(left, outerWidth), 0);

				img.style.left = (left * -xRatio) + 'px';
				img.style.top = (top * -yRatio) + 'px';
			}
		};
	};

	$.fn.zoom = function (options) {
		return this.each(function () {
			var
			settings = $.extend({}, defaults, options || {}),
			//target will display the zoomed iamge
			target = settings.target || this,
			//source will provide zoom location info (thumbnail)
			source = this,
			img = new Image(),
			$img = $(img),
			mousemove = 'mousemove',
			clicked = false;

			// If a url wasn't specified, look for an image element.
			if (!settings.url) {
				settings.url = $(source).find('img').attr('src');
				if (!settings.url) {
					return;
				}
			}

			img.onload = function () {
				$img.css({width:"auto",height:"auto"});
				var zoom = $.zoom(target, source, img);

				function start(e) {
					zoom.init();
					zoom.move(e);

					// Skip the fade-in for IE8 and lower since it chokes on fading-in
					// and changing position based on mousemovement at the same time.
					$img.stop()
					.fadeTo($.support.opacity ? settings.duration : 0, 1);
				}

				function stop() {
					$img.stop()
					.fadeTo(settings.duration, 0);
				}

				if (settings.on === 'grab') {
					$(source).on('mousedown',
						function (e) {
							$(document).one('mouseup',
								function () {
									stop();

									$(document).off(mousemove, zoom.move);
								}
							);

							start(e);

							$(document).on(mousemove, zoom.move);

							e.preventDefault();
						}
					);
				} else if (settings.on === 'click') {
					$(source).on('click',
						function (e) {
							if (clicked) {
								// bubble the event up to the document to trigger the unbind.
								return;
							} else {
								clicked = true;
								start(e);
								$(document).on(mousemove, zoom.move);
								$(document).one('click',
									function () {
										stop();
										clicked = false;
										$(document).off(mousemove, zoom.move);
									}
								);
								return false;
							}
						}
					);
				} else if (settings.on === 'toggle') {
					$(source).on('click',
						function (e) {
							if (clicked) {
								stop();
							} else {
								start(e);
							}
							clicked = !clicked;
						}
					);
				} else {
					zoom.init(); // Pre-emptively call init because IE7 will fire the mousemove handler before the hover handler.

					$(source)
						.on('mouseenter', start)
						.on('mouseleave', stop)
						.on(mousemove, zoom.move);
				}

				if ($.isFunction(settings.callback)) {
					settings.callback.call(img);
				}
			};

			img.src = settings.url;
		});
	};

	$.fn.zoom.defaults = defaults;
}(window.jQuery));<|endoftext|>


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

/** Adapt a synchronous method into a psedo-afunc. **/
Function.prototype.abind = function(self) {
  return function(ret) { this.apply(self, arguments); ret(); }.bind(this);
};

/** NOP afunc. **/
function anop(ret) { ret && ret(undefined); }

/** afunc log. **/
function alog() {
  var args = arguments;
  return function (ret) {
    console.log.apply(console, args);
    ret && ret.apply(this, [].shift.call(arguments));
  };
}

/** console.profile an afunc. **/
function aprofile(afunc) {
  return function(ret) {
     var a = argsToArray(arguments);
     console.profile();
     var ret2 = function () {
        console.profileEnd();
        ret && ret(arguments);
     };
     aapply_(afunc, ret2, a);
  };
}

/** Create an afunc which always returns the supplied constant value. **/
function aconstant(v) { return function(ret) { ret && ret(v); }; }

/** Execute the supplied afunc N times. **/
function arepeat(n, afunc) {
  if ( ! n ) return anop;
  return function(ret) {
    var a = argsToArray(arguments);
    a.splice(1, 0, 0, n); // insert (0, n) after 'ret' argument
    var next = atramp(function() {
      if ( a[1] == n-1 ) { a[0] = ret; afunc.apply(this, a); return; };
      afunc.apply(this, a);
      a[1]++;
    });

    a[0] = next;
    next.apply(this, a);
  };
}

/** Execute the supplied afunc on each element of an array. */
function aforEach(arr, afunc) {
  // TODO: implement
}

/** Execute the supplied afunc until cond() returns false. */
function awhile(cond, afunc) {
  return function(ret) {
    var a = argsToArray(arguments);

    var g = function() {
      if ( ! cond() ) { ret.apply(undefined, arguments); return; }
      afunc.apply(this, a);
    };

    a[0] = g;
    g.apply(this, a);
  };
}

/** Execute the supplied afunc if cond. */
function aif(cond, afunc) {
  return function(ret) {
    if ( cond ) {
       afunc.apply(this, arguments);
    } else {
       ret();
    }
  };
}

/** Time an afunc. **/
var atime = (function() {
  // Add a unique suffix to timer names in case multiple instances
  // of the same timing are active at once.
  var id = 1;
  var activeOps = {};
  return function (str, afunc, opt_callback) {
    return function(ret) {
      var name = str;
      if ( activeOps[str] ) {
         name += '-' + id++;
         activeOps[str]++;
      } else {
         activeOps[str] = 1;
      }
      var start = performance.now();
      console.time(name);
      var a = arguments;
      var args = [function() {
        activeOps[str]--;
        var end = performance.now();
        if ( opt_callback ) opt_callback(end-start);
        console.timeEnd(name);
        ret && ret.apply(this, [].shift.call(a));
      }];
      for ( var i = 1 ; i < a.length ; i++ ) args[i] = a[i];
      afunc.apply(this, args);
    };
  };
})();

/** Time an afunc and record its time as a metric. **/
var ametric = atime;

/** Sleep for the specified delay. **/
function asleep(ms) {
  return function(ret) {
    window.setTimeout(ret, ms);
  };
}

var ayield = asleep.bind(null, 0);

/** Create a future value. **/
function afuture() {
  var set     = false;
  var values  = undefined;
  var waiters = [];

  return {
    set: function() {
      if ( set ) {
        console.log('ERROR: redundant set on future');
        return;
      }
      values = arguments;
      set = true;
      for (var i = 0 ; i < waiters.length; i++) {
        waiters[i].apply(null, values);
      }
      waiters = undefined;
    },

    get: function(ret) {
      if ( set ) { ret.apply(null, values); return; }
      waiters.push(ret);
    }
  };
};

function aapply_(f, ret, args) {
  args.unshift(ret);
  f.apply(this, args);
}

/**
 * A Binary Semaphore which only allows the delegate function to be
 * executed by a single thread of execution at once.
 * Like Java's synchronized blocks.
 * @param opt_lock an empty map {} to be used as a lock
 *                 sharable across multiple asynchronized instances
 **/
function asynchronized(f, opt_lock) {
  var lock = opt_lock || {};
  if ( ! lock.q ) { lock.q = []; lock.active = false; }

  function onExit(g) {
    return function(ret) {
      var next = lock.q.shift();
      if ( next ) {
        setTimeout(next, 0);
      } else {
        lock.active = false;
      }

      g(ret);
    };
  }

  return function(ret) {
    if ( lock.active ) {
       lock.q.push(function() { f(onExit(ret)); onExit(ret); });
       return;
    }

    lock.active = true;

    f(onExit(ret));
  };
}

/**
 * Execute an optional timeout function and abort the continuation
 * of the delegate function, if it doesn't finish in the specified
 * time.
 **/
// Could be generalized into an afirst() combinator which allows
// for the execution of multiple streams but only the first to finish
// gets to continue.
function atimeout(delay, f, opt_timeoutF) {
  return function(ret) {
    var timedOut  = false;
    var completed = false;
    setTimeout(function() {
      if ( completed ) return;
      timedOut = true;
      console.log('timeout');
      opt_timeoutF && opt_timeoutF();
    }, delay);

    f(aseq(
      function(ret) {
        if ( ! timedOut ) completed = true;
        if ( completed ) ret();
      }, ret));
  };
}

/** Memoize an async function. **/
function amemo(f) {
  var memoized = false;
  var values;
  var waiters;

  return function(ret) {
    if ( memoized ) { ret.apply(null, values); return; }

    var first = ! waiters;

    if ( first ) waiters = [];

    waiters.push(ret);

    if ( first ) {
      f(function() {
        values = arguments;
        for (var i = 0 ; i < waiters.length; i++) {
          waiters[i] && waiters[i].apply(null, values);
        }
        f = undefined;
        memoized = true;
        waiters = undefined;
      });
    }
  };
}

/** Async Compose (like Function.prototype.O, but for async functions **/
Function.prototype.ao = function(f2) {
  var f1 = this;
  return function(ret) {
    var args = argsToArray(arguments);
    args[0] = f1.bind(this, ret);
    f2.apply(this, args);
  };
};

Function.prototype.aseq = function(f2) {
  return f2.ao(this);
};

/** Compose a variable number of async functions. **/
function ao(/* ... afuncs */) {
  var ret = arguments[arguments.length-1];

  for ( var i = 0 ; i < arguments.length-1 ; i++ ) {
    ret = arguments[i].ao(ret);
  }

  return ret;
}

/** Compose a variable number of async functions. **/
function aseq(/* ... afuncs */) {
  var f = arguments[arguments.length-1];

  for ( var i = arguments.length-2 ; i >= 0 ; i-- ) {
    f = arguments[i].aseq(f);
  }

  return f;
}

/**
 * Create a function which executes several afunc's in parallel and passes
 * their joined return values to an optional afunc.
 *
 * Usage: apar(f1,f2,f3)(opt_afunc, opt_args)
 * @param opt_afunc called with joined results after all afuncs finish
 * @param opt_args passed to all afuncs
 **/
function apar(/* ... afuncs */) {
  var aargs = [];
  var count = 0;
  var fs = arguments;

  return function(ret /* opt_args */) {
    if ( fs.length == 0 ) {
      ret && ret();
      return;
    }
    var opt_args = Array.prototype.splice.call(arguments, 1);
    var join = function (i) {
      aargs[i] = Array.prototype.splice.call(arguments, 1);
      if ( ++count == fs.length ) {
        var a = [];
        for ( var j = 0 ; j < fs.length ; j++ )
          for ( var k = 0 ; k < aargs[j].length ; k++ )
            a.push(aargs[j][k]);
        ret && ret.apply(null, a);
      }
    };

    for ( var i = 0 ; i < fs.length ; i++ )
      fs[i].apply(null, [join.bind(null, i)].concat(opt_args));
  };
}

/** Convert the supplied afunc into a trampolined-afunc. **/
var atramp = (function() {
   var active = false;
   var jobs = [];

   return function(afunc) {
      return function() {
         jobs.push([afunc, arguments]);
         if ( ! active ) {
if ( jobs.length > 1 ) debugger;
           active = true;
           var job;
           // Take responsibility for bouncing
           while ( (job = jobs.pop()) != null ) {
             job[0].apply(this, job[1]);
           }
           active = false;
         }
      };
   };
})();

/** Execute the supplied afunc concurrently n times. **/
function arepeatpar(n, afunc) {
  return function(ret /* opt_args */) {
    var aargs = [];
    var count = 0;

    var opt_args = Array.prototype.splice.call(arguments, 1);
    var join = function (i) {
      // aargs[i] = Array.prototype.splice.call(arguments, 1);
      if ( ++count == n ) {
        var a = [];
        /*
        for ( var j = 0 ; j < n ; j++ )
          for ( var k = 0 ; k < aargs[j].length ; k++ )
            a.push(aargs[j][k]);
         */
        ret && ret.apply(null, a);
      }
    };

    for ( var i = 0 ; i < n ; i++ ) {
      afunc.apply(null, [join.bind(null, i)].concat([i, n]).concat(opt_args));
    }
  };
}

function axhr(url, opt_op, opt_params) {
  var op = opt_op || "GET";
  var params = opt_params || [];

  return function(ret) {
    var xhr = new XMLHttpRequest();
    xhr.open(op, url);
    xhr.asend(function(json) { ret(JSON.parse(json)); }, params && params.join('&'));
  };
}

// Note: this doesn't work for packaged-apps
var __JSONP_CALLBACKS__ = {};
var ajsonp = (function() {
  var nextID = 0;

  return function(url, params) {
    return function(ret) {
      var id = 'c' + (nextID++);

      __JSONP_CALLBACKS__[id] = function(data) {
        delete __JSONP_CALLBACKS__[id];

        // console.log('JSONP Callback', id, data);

        ret && ret.call(this, data);
      };

      var script = document.createElement('script');
      script.src = url + '?callback=__JSONP_CALLBACKS__.' + id + (params ? '&' + params.join('&') : '');
      script.onload = function() {
        document.body.removeChild(this);
      };
      document.body.appendChild(script);
    };
  };
})();

function futurefn(future) {
  return function() {
    var args = arguments;
    future.get(function(f) {
      f.apply(undefined, args);
    });
  };
}
<commit_msg>Simplified asynchronized. FUN:asynchronized.<commit_after>/**
 * @license
 * Copyright 2013 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// TODO: time-travelling debugger, ala:
//    "Debugging Standard ML Without Reverse Engineering"

/** Adapt a synchronous method into a psedo-afunc. **/
Function.prototype.abind = function(self) {
  return function(ret) { this.apply(self, arguments); ret(); }.bind(this);
};

/** NOP afunc. **/
function anop(ret) { ret && ret(undefined); }

/** afunc log. **/
function alog() {
  var args = arguments;
  return function (ret) {
    console.log.apply(console, args);
    ret && ret.apply(this, [].shift.call(arguments));
  };
}

/** console.profile an afunc. **/
function aprofile(afunc) {
  return function(ret) {
     var a = argsToArray(arguments);
     console.profile();
     var ret2 = function () {
        console.profileEnd();
        ret && ret(arguments);
     };
     aapply_(afunc, ret2, a);
  };
}

/** Create an afunc which always returns the supplied constant value. **/
function aconstant(v) { return function(ret) { ret && ret(v); }; }

/** Execute the supplied afunc N times. **/
function arepeat(n, afunc) {
  if ( ! n ) return anop;
  return function(ret) {
    var a = argsToArray(arguments);
    a.splice(1, 0, 0, n); // insert (0, n) after 'ret' argument
    var next = atramp(function() {
      if ( a[1] == n-1 ) { a[0] = ret; afunc.apply(this, a); return; };
      afunc.apply(this, a);
      a[1]++;
    });

    a[0] = next;
    next.apply(this, a);
  };
}

/** Execute the supplied afunc on each element of an array. */
function aforEach(arr, afunc) {
  // TODO: implement
}

/** Execute the supplied afunc until cond() returns false. */
function awhile(cond, afunc) {
  return function(ret) {
    var a = argsToArray(arguments);

    var g = function() {
      if ( ! cond() ) { ret.apply(undefined, arguments); return; }
      afunc.apply(this, a);
    };

    a[0] = g;
    g.apply(this, a);
  };
}

/** Execute the supplied afunc if cond. */
function aif(cond, afunc) {
  return function(ret) {
    if ( cond ) {
       afunc.apply(this, arguments);
    } else {
       ret();
    }
  };
}

/** Time an afunc. **/
var atime = (function() {
  // Add a unique suffix to timer names in case multiple instances
  // of the same timing are active at once.
  var id = 1;
  var activeOps = {};
  return function (str, afunc, opt_callback) {
    return function(ret) {
      var name = str;
      if ( activeOps[str] ) {
         name += '-' + id++;
         activeOps[str]++;
      } else {
         activeOps[str] = 1;
      }
      var start = performance.now();
      console.time(name);
      var a = arguments;
      var args = [function() {
        activeOps[str]--;
        var end = performance.now();
        if ( opt_callback ) opt_callback(end-start);
        console.timeEnd(name);
        ret && ret.apply(this, [].shift.call(a));
      }];
      for ( var i = 1 ; i < a.length ; i++ ) args[i] = a[i];
      afunc.apply(this, args);
    };
  };
})();

/** Time an afunc and record its time as a metric. **/
var ametric = atime;

/** Sleep for the specified delay. **/
function asleep(ms) {
  return function(ret) {
    window.setTimeout(ret, ms);
  };
}

var ayield = asleep.bind(null, 0);

/** Create a future value. **/
function afuture() {
  var set     = false;
  var values  = undefined;
  var waiters = [];

  return {
    set: function() {
      if ( set ) {
        console.log('ERROR: redundant set on future');
        return;
      }
      values = arguments;
      set = true;
      for (var i = 0 ; i < waiters.length; i++) {
        waiters[i].apply(null, values);
      }
      waiters = undefined;
    },

    get: function(ret) {
      if ( set ) { ret.apply(null, values); return; }
      waiters.push(ret);
    }
  };
};

function aapply_(f, ret, args) {
  args.unshift(ret);
  f.apply(this, args);
}

/**
 * A Binary Semaphore which only allows the delegate function to be
 * executed by a single thread of execution at once.
 * Like Java's synchronized blocks.
 * @param opt_lock an empty map {} to be used as a lock
 *                 sharable across multiple asynchronized instances
 **/
function asynchronized(f, opt_lock) {
  var lock = opt_lock || {};
  if ( ! lock.q ) { lock.q = []; lock.active = false; }

  // Decorate 'ret' to check for blocked continuations.
  function onExit(ret) {
    return function() {
      var next = lock.q.shift();

      if ( next ) {
        setTimeout(next, 0);
      } else {
        lock.active = false;
      }

      ret();
    };
  }

  return function(ret) {
    // Semaphore is in use, so just queue f for execution when the current
    // continuation exits.
    if ( lock.active ) {
       lock.q.push(function() { f(onExit(ret)); });
       return;
    }

    lock.active = true;

    f(onExit(ret));
  };
}

/**
 * Execute an optional timeout function and abort the continuation
 * of the delegate function, if it doesn't finish in the specified
 * time.
 **/
// Could be generalized into an afirst() combinator which allows
// for the execution of multiple streams but only the first to finish
// gets to continue.
function atimeout(delay, f, opt_timeoutF) {
  return function(ret) {
    var timedOut  = false;
    var completed = false;
    setTimeout(function() {
      if ( completed ) return;
      timedOut = true;
      console.log('timeout');
      opt_timeoutF && opt_timeoutF();
    }, delay);

    f(aseq(
      function(ret) {
        if ( ! timedOut ) completed = true;
        if ( completed ) ret();
      }, ret));
  };
}

/** Memoize an async function. **/
function amemo(f) {
  var memoized = false;
  var values;
  var waiters;

  return function(ret) {
    if ( memoized ) { ret.apply(null, values); return; }

    var first = ! waiters;

    if ( first ) waiters = [];

    waiters.push(ret);

    if ( first ) {
      f(function() {
        values = arguments;
        for (var i = 0 ; i < waiters.length; i++) {
          waiters[i] && waiters[i].apply(null, values);
        }
        f = undefined;
        memoized = true;
        waiters = undefined;
      });
    }
  };
}

/** Async Compose (like Function.prototype.O, but for async functions **/
Function.prototype.ao = function(f2) {
  var f1 = this;
  return function(ret) {
    var args = argsToArray(arguments);
    args[0] = f1.bind(this, ret);
    f2.apply(this, args);
  };
};

Function.prototype.aseq = function(f2) {
  return f2.ao(this);
};

/** Compose a variable number of async functions. **/
function ao(/* ... afuncs */) {
  var ret = arguments[arguments.length-1];

  for ( var i = 0 ; i < arguments.length-1 ; i++ ) {
    ret = arguments[i].ao(ret);
  }

  return ret;
}

/** Compose a variable number of async functions. **/
function aseq(/* ... afuncs */) {
  var f = arguments[arguments.length-1];

  for ( var i = arguments.length-2 ; i >= 0 ; i-- ) {
    f = arguments[i].aseq(f);
  }

  return f;
}

/**
 * Create a function which executes several afunc's in parallel and passes
 * their joined return values to an optional afunc.
 *
 * Usage: apar(f1,f2,f3)(opt_afunc, opt_args)
 * @param opt_afunc called with joined results after all afuncs finish
 * @param opt_args passed to all afuncs
 **/
function apar(/* ... afuncs */) {
  var aargs = [];
  var count = 0;
  var fs = arguments;

  return function(ret /* opt_args */) {
    if ( fs.length == 0 ) {
      ret && ret();
      return;
    }
    var opt_args = Array.prototype.splice.call(arguments, 1);
    var join = function (i) {
      aargs[i] = Array.prototype.splice.call(arguments, 1);
      if ( ++count == fs.length ) {
        var a = [];
        for ( var j = 0 ; j < fs.length ; j++ )
          for ( var k = 0 ; k < aargs[j].length ; k++ )
            a.push(aargs[j][k]);
        ret && ret.apply(null, a);
      }
    };

    for ( var i = 0 ; i < fs.length ; i++ )
      fs[i].apply(null, [join.bind(null, i)].concat(opt_args));
  };
}

/** Convert the supplied afunc into a trampolined-afunc. **/
var atramp = (function() {
   var active = false;
   var jobs = [];

   return function(afunc) {
      return function() {
         jobs.push([afunc, arguments]);
         if ( ! active ) {
if ( jobs.length > 1 ) debugger;
           active = true;
           var job;
           // Take responsibility for bouncing
           while ( (job = jobs.pop()) != null ) {
             job[0].apply(this, job[1]);
           }
           active = false;
         }
      };
   };
})();

/** Execute the supplied afunc concurrently n times. **/
function arepeatpar(n, afunc) {
  return function(ret /* opt_args */) {
    var aargs = [];
    var count = 0;

    var opt_args = Array.prototype.splice.call(arguments, 1);
    var join = function (i) {
      // aargs[i] = Array.prototype.splice.call(arguments, 1);
      if ( ++count == n ) {
        var a = [];
        /*
        for ( var j = 0 ; j < n ; j++ )
          for ( var k = 0 ; k < aargs[j].length ; k++ )
            a.push(aargs[j][k]);
         */
        ret && ret.apply(null, a);
      }
    };

    for ( var i = 0 ; i < n ; i++ ) {
      afunc.apply(null, [join.bind(null, i)].concat([i, n]).concat(opt_args));
    }
  };
}

function axhr(url, opt_op, opt_params) {
  var op = opt_op || "GET";
  var params = opt_params || [];

  return function(ret) {
    var xhr = new XMLHttpRequest();
    xhr.open(op, url);
    xhr.asend(function(json) { ret(JSON.parse(json)); }, params && params.join('&'));
  };
}

// Note: this doesn't work for packaged-apps
var __JSONP_CALLBACKS__ = {};
var ajsonp = (function() {
  var nextID = 0;

  return function(url, params) {
    return function(ret) {
      var id = 'c' + (nextID++);

      __JSONP_CALLBACKS__[id] = function(data) {
        delete __JSONP_CALLBACKS__[id];

        // console.log('JSONP Callback', id, data);

        ret && ret.call(this, data);
      };

      var script = document.createElement('script');
      script.src = url + '?callback=__JSONP_CALLBACKS__.' + id + (params ? '&' + params.join('&') : '');
      script.onload = function() {
        document.body.removeChild(this);
      };
      document.body.appendChild(script);
    };
  };
})();

function futurefn(future) {
  return function() {
    var args = arguments;
    future.get(function(f) {
      f.apply(undefined, args);
    });
  };
}
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Check box.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // imports
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );

  /**
   * @param {Node} content
   * @param {Property<Boolean>} property
   * @constructor
   * @param options
   */
  function CheckBox( content, property, options ) {
    var checkBox = this;
    options = _.extend(
      {
        spacing: 5,
        boxScale: 0.6,
        cursor: 'pointer',
        checkBoxColor: 'black'
      }, options );

    var thisNode = this;
    Node.call( this );

    var x = options.boxScale / 0.75;

    //Make the background white.  Until we are creating our own shapes, just put a white rectangle behind the font awesome check box icons
    var whiteBackground = new Rectangle( 0, -25 * x, 25 * x, 25 * x, 5 * x, 5 * x, {fill: 'white'} );

    var checkedNode = new FontAwesomeNode( 'check', { scale: options.boxScale, fill: options.checkBoxColor } );
    var uncheckedNode = new FontAwesomeNode( 'check_empty', { scale: options.boxScale, fill: options.checkBoxColor } );

    thisNode.addChild( whiteBackground );
    thisNode.addChild( checkedNode );
    thisNode.addChild( uncheckedNode );
    thisNode.addChild( content );

    content.left = checkedNode.right + options.spacing;
    content.centerY = checkedNode.centerY;

    // put a rectangle on top of everything to prevent dead zones which clicking
    thisNode.addChild( new Rectangle( thisNode.left, thisNode.top, thisNode.width, thisNode.height ) );

    // interactivity
    thisNode.addInputListener( new ButtonListener( {
      fire: function() {
        property.value = !property.value;
      }
    } ) );

    // sync with property
    property.link( function( checked ) {
      checkedNode.visible = checked;
      uncheckedNode.visible = !checked;
    } );

    //Add accessibility
    this.addPeer( '<input type="checkbox">', {click: function() {property.value = !property.value;}, label: options.label} );
    property.link( function( value ) {
      _.each( checkBox.instances, function( instance ) {

        //Make sure accessibility is enabled, then apply the change to the peer
        _.each( instance.peers, function( peer ) {
          peer.element.setAttribute( 'checked', value );
        } );
      } );
    } );

    // Apply additional options
    this.mutate( options );
  }

  return inherit( Node, CheckBox );
} );<commit_msg>use touchArea instead of a Rectangle overlay, and allow optional padding<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Check box.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // imports
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );

  /**
   * @param {Node} content
   * @param {Property<Boolean>} property
   * @constructor
   * @param options
   */
  function CheckBox( content, property, options ) {
    var checkBox = this;
    options = _.extend(
      {
        spacing: 5,
        boxScale: 0.6,
        cursor: 'pointer',
        checkBoxColor: 'black',

        //TODO: Should these default to something larger?
        touchAreaTopPadding: 0,
        touchAreaBottomPadding: 0,
        touchAreaLeftPadding: 0,
        touchAreaRightPadding: 0
      }, options );

    var thisNode = this;
    Node.call( this );

    var x = options.boxScale / 0.75;

    //Make the background white.  Until we are creating our own shapes, just put a white rectangle behind the font awesome check box icons
    var whiteBackground = new Rectangle( 0, -25 * x, 25 * x, 25 * x, 5 * x, 5 * x, {fill: 'white'} );

    var checkedNode = new FontAwesomeNode( 'check', { scale: options.boxScale, fill: options.checkBoxColor } );
    var uncheckedNode = new FontAwesomeNode( 'check_empty', { scale: options.boxScale, fill: options.checkBoxColor } );

    thisNode.addChild( whiteBackground );
    thisNode.addChild( checkedNode );
    thisNode.addChild( uncheckedNode );
    thisNode.addChild( content );

    content.left = checkedNode.right + options.spacing;
    content.centerY = checkedNode.centerY;

    // put a rectangle on top of everything to prevent dead zones which clicking
    thisNode.mouseArea = thisNode.touchArea = Shape.rectangle( thisNode.left - options.touchAreaLeftPadding, thisNode.top - options.touchAreaTopPadding, thisNode.width + options.touchAreaLeftPadding + options.touchAreaRightPadding, thisNode.height + options.touchAreaTopPadding + options.touchAreaBottomPadding );

    // interactivity
    thisNode.addInputListener( new ButtonListener( {
      fire: function() {
        property.value = !property.value;
      }
    } ) );

    // sync with property
    property.link( function( checked ) {
      checkedNode.visible = checked;
      uncheckedNode.visible = !checked;
    } );

    //Add accessibility
    this.addPeer( '<input type="checkbox">', {click: function() {property.value = !property.value;}, label: options.label} );
    property.link( function( value ) {
      _.each( checkBox.instances, function( instance ) {

        //Make sure accessibility is enabled, then apply the change to the peer
        _.each( instance.peers, function( peer ) {
          peer.element.setAttribute( 'checked', value );
        } );
      } );
    } );

    // Apply additional options
    this.mutate( options );
  }

  return inherit( Node, CheckBox );
} );<|endoftext|>


<commit_before>// Copyright 2013-2021, University of Colorado Boulder

/**
 * Checkbox is a typical checkbox UI component.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

import Action from '../../axon/js/Action.js';
import Property from '../../axon/js/Property.js';
import TinyProperty from '../../axon/js/TinyProperty.js';
import validate from '../../axon/js/validate.js';
import Matrix3 from '../../dot/js/Matrix3.js';
import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import merge from '../../phet-core/js/merge.js';
import { Voicing } from '../../scenery/js/imports.js';
import { FireListener } from '../../scenery/js/imports.js';
import { Node } from '../../scenery/js/imports.js';
import { Path } from '../../scenery/js/imports.js';
import { Rectangle } from '../../scenery/js/imports.js';
import { SceneryConstants } from '../../scenery/js/imports.js';
import checkEmptySolidShape from '../../sherpa/js/fontawesome-4/checkEmptySolidShape.js';
import checkSquareOSolidShape from '../../sherpa/js/fontawesome-4/checkSquareOSolidShape.js';
import checkboxCheckedSoundPlayer from '../../tambo/js/shared-sound-players/checkboxCheckedSoundPlayer.js';
import checkboxUncheckedSoundPlayer from '../../tambo/js/shared-sound-players/checkboxUncheckedSoundPlayer.js';
import EventType from '../../tandem/js/EventType.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import sun from './sun.js';

// constants
const BOOLEAN_VALIDATOR = { valueType: 'boolean' };
const SHAPE_MATRIX = Matrix3.createFromPool( 0.025, 0, 0, 0, -0.025, 0, 0, 0, 1 ); // to create a unity-scale icon
const uncheckedShape = checkEmptySolidShape.transformed( SHAPE_MATRIX );
const checkedShape = checkSquareOSolidShape.transformed( SHAPE_MATRIX );

class Checkbox extends Node {

  /**
   * @param {Node} content
   * @param {Property.<boolean>} property
   * @param {Object} [options]
   * @mixes {Voicing}
   */
  constructor( content, property, options ) {

    assert && assert( content instanceof Node, 'invalid content' );
    assert && assert( property instanceof Property || property instanceof TinyProperty, 'invalid property' );
    validate( property.value, BOOLEAN_VALIDATOR );

    options = merge( {
      spacing: 5,
      boxWidth: 21,
      cursor: 'pointer',
      checkboxColor: 'black',
      checkboxColorBackground: 'white',
      disabledOpacity: SceneryConstants.DISABLED_OPACITY,

      // pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      // phet-io
      tandem: Tandem.REQUIRED,
      phetioEventType: EventType.USER,
      phetioLinkProperty: true, // whether a link to the checkbox's Property is created
      visiblePropertyOptions: { phetioFeatured: true },
      phetioEnabledPropertyInstrumented: true, // opt into default PhET-iO instrumented enabledProperty

      // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly,

      // {SoundPlayer} - sound generators
      checkedSoundPlayer: checkboxCheckedSoundPlayer,
      uncheckedSoundPlayer: checkboxUncheckedSoundPlayer,

      // pdom
      tagName: 'input',
      inputType: 'checkbox',
      appendDescription: true
    }, options );

    super();

    // voicing - initialize the Trait
    this.initializeVoicing();

    // @private - sends out notifications when the checkbox is toggled.
    const toggleAction = new Action( () => {
      property.value = !property.value;
      validate( property.value, BOOLEAN_VALIDATOR );
      if ( property.value ) {
        options.checkedSoundPlayer.play();
      }
      else {
        options.uncheckedSoundPlayer.play();
      }
    }, {
      parameters: [],
      tandem: options.tandem.createTandem( 'toggleAction' ),
      phetioDocumentation: 'Emits when user input causes the checkbox to toggle, emitting a single arg: ' +
                           'the new boolean value of the checkbox state.',
      phetioReadOnly: true, // interoperability should be done through the Property, this is just for the data stream event.
      phetioEventType: EventType.USER
    } );

    // @private - Create the background.  Until we are creating our own shapes, just put a rectangle behind the font
    // awesome checkbox icons.
    this.backgroundNode = new Rectangle( 0, -options.boxWidth, options.boxWidth * 0.95, options.boxWidth * 0.95,
      options.boxWidth * 0.2, options.boxWidth * 0.2, {
        fill: options.checkboxColorBackground
      } );

    // @private
    this.uncheckedNode = new Path( uncheckedShape, {
      fill: options.checkboxColor
    } );
    const iconScale = options.boxWidth / this.uncheckedNode.width;
    this.uncheckedNode.scale( iconScale );

    // @private
    this.checkedNode = new Path( checkedShape, {
      scale: iconScale,
      fill: options.checkboxColor
    } );

    // @private
    this.checkboxNode = new Node( { children: [ this.backgroundNode, this.checkedNode, this.uncheckedNode ] } );

    this.addChild( this.checkboxNode );
    this.addChild( content );

    content.left = this.checkedNode.right + options.spacing;
    content.centerY = this.checkedNode.centerY;

    // put a rectangle on top of everything to prevent dead zones when clicking
    this.addChild( new Rectangle( this.left, this.top, this.width, this.height ) );

    content.pickable = false; // since there's a pickable rectangle on top of content

    // interactivity
    const fireListener = new FireListener( {
      fire: () => toggleAction.execute(),
      tandem: options.tandem.createTandem( 'fireListener' )
    } );
    this.addInputListener( fireListener );

    // sync with property
    const checkboxCheckedListener = checked => {
      this.checkedNode.visible = checked;
      this.uncheckedNode.visible = !checked;
      this.pdomChecked = checked;
    };
    property.link( checkboxCheckedListener );

    // Apply additional options
    this.mutate( options );

    // pointer areas
    this.touchArea = this.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
    this.mouseArea = this.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

    // pdom - to prevent a bug with NVDA and Firefox where the label sibling receives two click events, see
    // https://github.com/phetsims/gravity-force-lab/issues/257
    this.setExcludeLabelSiblingFromInput();

    // must be after the Checkbox is instrumented
    options.phetioLinkProperty && this.addLinkedElement( property, {
      tandem: options.tandem.createTandem( 'property' )
    } );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'Checkbox', this );

    // @private
    this.disposeCheckbox = () => {

      fireListener.dispose();

      if ( property.hasListener( checkboxCheckedListener ) ) {
        property.unlink( checkboxCheckedListener );
      }

      // Private to Checkbox, but we need to clean up tandem.
      toggleAction.dispose();
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeCheckbox();
    super.dispose();
  }

  /**
   * Sets the background color of the checkbox.
   * @param {Color|String} value
   * @public
   */
  setCheckboxColorBackground( value ) { this.backgroundNode.fill = value; }

  set checkboxColorBackground( value ) { this.setCheckboxColorBackground( value ); }

  /**
   * Gets the background color of the checkbox.
   * @returns {Color|String}
   * @public
   */
  getCheckboxColorBackground() { return this.backgroundNode.fill; }

  get checkboxColorBackground() { return this.getCheckboxColorBackground(); }

  /**
   * Sets the color of the checkbox.
   * @param {Color|String} value
   * @public
   */
  setCheckboxColor( value ) { this.checkedNode.fill = this.uncheckedNode.fill = value; }

  set checkboxColor( value ) { this.setCheckboxColor( value ); }

  /**
   * Gets the color of the checkbox.
   * @returns {Color|String}
   * @public
   */
  getCheckboxColor() { return this.checkedNode.fill; }

  get checkboxColor() { return this.getCheckboxColor(); }
}

Voicing.compose( Checkbox );

sun.register( 'Checkbox', Checkbox );
export default Checkbox;<commit_msg>Add Utterances to be spoken by a screen reader when the Checkbox is pressed, see https://github.com/phetsims/sun/issues/734<commit_after>// Copyright 2013-2021, University of Colorado Boulder

/**
 * Checkbox is a typical checkbox UI component.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

import Action from '../../axon/js/Action.js';
import Property from '../../axon/js/Property.js';
import TinyProperty from '../../axon/js/TinyProperty.js';
import validate from '../../axon/js/validate.js';
import Matrix3 from '../../dot/js/Matrix3.js';
import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import merge from '../../phet-core/js/merge.js';
import { Voicing } from '../../scenery/js/imports.js';
import { FireListener } from '../../scenery/js/imports.js';
import { Node } from '../../scenery/js/imports.js';
import { Path } from '../../scenery/js/imports.js';
import { Rectangle } from '../../scenery/js/imports.js';
import { SceneryConstants } from '../../scenery/js/imports.js';
import checkEmptySolidShape from '../../sherpa/js/fontawesome-4/checkEmptySolidShape.js';
import checkSquareOSolidShape from '../../sherpa/js/fontawesome-4/checkSquareOSolidShape.js';
import checkboxCheckedSoundPlayer from '../../tambo/js/shared-sound-players/checkboxCheckedSoundPlayer.js';
import checkboxUncheckedSoundPlayer from '../../tambo/js/shared-sound-players/checkboxUncheckedSoundPlayer.js';
import EventType from '../../tandem/js/EventType.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import sun from './sun.js';

// constants
const BOOLEAN_VALIDATOR = { valueType: 'boolean' };
const SHAPE_MATRIX = Matrix3.createFromPool( 0.025, 0, 0, 0, -0.025, 0, 0, 0, 1 ); // to create a unity-scale icon
const uncheckedShape = checkEmptySolidShape.transformed( SHAPE_MATRIX );
const checkedShape = checkSquareOSolidShape.transformed( SHAPE_MATRIX );

class Checkbox extends Node {

  /**
   * @param {Node} content
   * @param {Property.<boolean>} property
   * @param {Object} [options]
   * @mixes {Voicing}
   */
  constructor( content, property, options ) {

    assert && assert( content instanceof Node, 'invalid content' );
    assert && assert( property instanceof Property || property instanceof TinyProperty, 'invalid property' );
    validate( property.value, BOOLEAN_VALIDATOR );

    options = merge( {
      spacing: 5,
      boxWidth: 21,
      cursor: 'pointer',
      checkboxColor: 'black',
      checkboxColorBackground: 'white',
      disabledOpacity: SceneryConstants.DISABLED_OPACITY,

      // pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      // phet-io
      tandem: Tandem.REQUIRED,
      phetioEventType: EventType.USER,
      phetioLinkProperty: true, // whether a link to the checkbox's Property is created
      visiblePropertyOptions: { phetioFeatured: true },
      phetioEnabledPropertyInstrumented: true, // opt into default PhET-iO instrumented enabledProperty

      // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly,

      // {SoundPlayer} - sound generators
      checkedSoundPlayer: checkboxCheckedSoundPlayer,
      uncheckedSoundPlayer: checkboxUncheckedSoundPlayer,

      // pdom
      tagName: 'input',
      inputType: 'checkbox',
      appendDescription: true,

      // {Utterance|null} - Utterances to be spoken with a screen reader after the checkbox is pressed.
      checkedContextResponseUtterance: null,
      uncheckedContextResponseUtterance: null
    }, options );

    super();

    // voicing - initialize the Trait
    this.initializeVoicing();

    // @private - sends out notifications when the checkbox is toggled.
    const toggleAction = new Action( () => {
      property.value = !property.value;
      validate( property.value, BOOLEAN_VALIDATOR );
      if ( property.value ) {
        options.checkedSoundPlayer.play();
        options.checkedContextResponseUtterance && this.alertDescriptionUtterance( options.checkedContextResponseUtterance );
      }
      else {
        options.uncheckedSoundPlayer.play();
        options.uncheckedContextResponseUtterance && this.alertDescriptionUtterance( options.uncheckedContextResponseUtterance );
      }
    }, {
      parameters: [],
      tandem: options.tandem.createTandem( 'toggleAction' ),
      phetioDocumentation: 'Emits when user input causes the checkbox to toggle, emitting a single arg: ' +
                           'the new boolean value of the checkbox state.',
      phetioReadOnly: true, // interoperability should be done through the Property, this is just for the data stream event.
      phetioEventType: EventType.USER
    } );

    // @private - Create the background.  Until we are creating our own shapes, just put a rectangle behind the font
    // awesome checkbox icons.
    this.backgroundNode = new Rectangle( 0, -options.boxWidth, options.boxWidth * 0.95, options.boxWidth * 0.95,
      options.boxWidth * 0.2, options.boxWidth * 0.2, {
        fill: options.checkboxColorBackground
      } );

    // @private
    this.uncheckedNode = new Path( uncheckedShape, {
      fill: options.checkboxColor
    } );
    const iconScale = options.boxWidth / this.uncheckedNode.width;
    this.uncheckedNode.scale( iconScale );

    // @private
    this.checkedNode = new Path( checkedShape, {
      scale: iconScale,
      fill: options.checkboxColor
    } );

    // @private
    this.checkboxNode = new Node( { children: [ this.backgroundNode, this.checkedNode, this.uncheckedNode ] } );

    this.addChild( this.checkboxNode );
    this.addChild( content );

    content.left = this.checkedNode.right + options.spacing;
    content.centerY = this.checkedNode.centerY;

    // put a rectangle on top of everything to prevent dead zones when clicking
    this.addChild( new Rectangle( this.left, this.top, this.width, this.height ) );

    content.pickable = false; // since there's a pickable rectangle on top of content

    // interactivity
    const fireListener = new FireListener( {
      fire: () => toggleAction.execute(),
      tandem: options.tandem.createTandem( 'fireListener' )
    } );
    this.addInputListener( fireListener );

    // sync with property
    const checkboxCheckedListener = checked => {
      this.checkedNode.visible = checked;
      this.uncheckedNode.visible = !checked;
      this.pdomChecked = checked;
    };
    property.link( checkboxCheckedListener );

    // Apply additional options
    this.mutate( options );

    // pointer areas
    this.touchArea = this.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
    this.mouseArea = this.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

    // pdom - to prevent a bug with NVDA and Firefox where the label sibling receives two click events, see
    // https://github.com/phetsims/gravity-force-lab/issues/257
    this.setExcludeLabelSiblingFromInput();

    // must be after the Checkbox is instrumented
    options.phetioLinkProperty && this.addLinkedElement( property, {
      tandem: options.tandem.createTandem( 'property' )
    } );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'Checkbox', this );

    // @private
    this.disposeCheckbox = () => {

      fireListener.dispose();

      if ( property.hasListener( checkboxCheckedListener ) ) {
        property.unlink( checkboxCheckedListener );
      }

      // Private to Checkbox, but we need to clean up tandem.
      toggleAction.dispose();
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeCheckbox();
    super.dispose();
  }

  /**
   * Sets the background color of the checkbox.
   * @param {Color|String} value
   * @public
   */
  setCheckboxColorBackground( value ) { this.backgroundNode.fill = value; }

  set checkboxColorBackground( value ) { this.setCheckboxColorBackground( value ); }

  /**
   * Gets the background color of the checkbox.
   * @returns {Color|String}
   * @public
   */
  getCheckboxColorBackground() { return this.backgroundNode.fill; }

  get checkboxColorBackground() { return this.getCheckboxColorBackground(); }

  /**
   * Sets the color of the checkbox.
   * @param {Color|String} value
   * @public
   */
  setCheckboxColor( value ) { this.checkedNode.fill = this.uncheckedNode.fill = value; }

  set checkboxColor( value ) { this.setCheckboxColor( value ); }

  /**
   * Gets the color of the checkbox.
   * @returns {Color|String}
   * @public
   */
  getCheckboxColor() { return this.checkedNode.fill; }

  get checkboxColor() { return this.getCheckboxColor(); }
}

Voicing.compose( Checkbox );

sun.register( 'Checkbox', Checkbox );
export default Checkbox;<|endoftext|>


<commit_before>var argv = require('yargs')
    .usage('Usage: $0 -w [websocket port] -p [http admin port]')
    .demand(['w','p'])
    .describe('w', 'Websocket port')
    .describe('p', 'HTTP admin port')
    .argv;

var REDIS_NOTIFICATION_CHANNEL = 'notifications';
var REDIS_SESSION_PREFIX = 'session-store-';
var REDIS_KEYSPACE_CHANNEL = '__keyspace@0__:' + REDIS_SESSION_PREFIX + '*';
var WEBSOCKET_PORT = argv.w;
var HTTP_ADMIN_PORT = argv.p;
var SOCKETIO_CHANNEL = 'message';

var io = require('socket.io')({
    path: '/socket',
    serveClient: false
});

var adminServer = require('./admin-server')(io);

var redis = require('redis').createClient();
var redisSubscriber = require('redis').createClient();

redisSubscriber.config('SET', 'notify-keyspace-events', 'KA');

var winston = require('winston');
var logger = new winston.Logger({
    transports: [
      new (winston.transports.Console)({
        timestamp: function() {
          return new Date().toISOString();
        }
      })
    ]
});

logger.info('starting');

/*

    incoming websocket connections arrive here

    the client needs to send an "authenticate" message with:

        {
            "sessionId" : "<some session id>"
        }

    we check redis to see if we have a session/userid registered
    for this. otherwise we just leave the connection open, and hook it
    up later if we get notified from redis.

*/

io.on('connection', function(socket) {

    socket.data = {};

    logger.info('connection', socket.id, 'connected', socket.handshake);

    // when they emit an authenticate message, check they are ok

    socket.on('authenticate', function(data) {

        logger.info('connection', socket.id, 'authenticating', data);

        socket.data.sessionId = data.sessionId;

        getUserIdFor(data.sessionId, function(err, userId) {
            if (err) {
                logger.info('connection', socket.id, 'session', data.sessionId, 'has no logged-in user');
                return;
            }
            updateSocketUser(socket, userId);
        });

    });

    socket.on('disconnect', function() {
        logger.info('connection', socket.id, 'disconnected');
        logger.info('connection count', io.sockets.sockets.length);
    });

});

function updateSocketUser(socket, userId) {

    if (userId && socket.data.userId !== userId) {
        var joinRoom = roomFor(userId);
        logger.info('connection', socket.id, 'joining [' + joinRoom + ']');
        socket.join(joinRoom);
    }

    if (socket.data.userId !== undefined &&
        socket.data.userId !== userId) {
        var leaveRoom = roomFor(socket.data.userId);
        logger.info('connection', socket.id, 'leaving [' + leaveRoom + ']');
        socket.leave(leaveRoom);
    }

    socket.data.userId = userId;
}

/*

    subscribe redis to listen for messages on the notification channel

    these will then be pushed to the relevent users

    the content is a json object like:

        {
            "users": [43, 35, 35],
            "type": "some_message_type",
            "payload": {
                "stuff" : "for",
                "the"   : "users"
            }
        }

*/

redisSubscriber.subscribe(REDIS_NOTIFICATION_CHANNEL);
logger.info('subscribed to redis channel', REDIS_NOTIFICATION_CHANNEL);

redisSubscriber.on('message', function(channel, str) {
    if (channel === REDIS_NOTIFICATION_CHANNEL) {
        try {
            var message = JSON.parse(str);
            if (message.users === undefined || message.users.constructor !== Array) {
                logger.info('discarding message as it does not specify a user array', message);
                return;
            } else if (message.payload === undefined) {
                logger.info('discarding message as it does not have a payload', message);
                return;
            } else {
                var users = message.users;
                delete message.users; // the remaining fields go to each user
                logger.info('publishing [', message, ']', 'to users', users.join(','));
                users.forEach(function(userId){
                    console.log('publishing to', roomFor(userId));
                    io.to(roomFor(userId)).emit(SOCKETIO_CHANNEL, message);
                });
            }
        } catch (e) {
            logger.error('failed to parsed json from redis [' + str + ']', e.message);
        }
    } else {
        logger.info('got message on unknown channel', channel, str);
    }
});

/*

    we listen for key events on redis so that we can:

        1) know when when a session/userid pair is added to redis
        2) know when a session is removed from redis

*/

redisSubscriber.psubscribe(REDIS_KEYSPACE_CHANNEL);
logger.info('subscribed to redis channel', REDIS_KEYSPACE_CHANNEL);

redisSubscriber.on('pmessage', function(pattern, channel, action){
    var m = new RegExp(':' + REDIS_SESSION_PREFIX + '(.*)$').exec(channel);
    if (!m) return;
    var sessionId = m[1];
    if (action === 'set') {

        getUserIdFor(sessionId, function(err, userId){
            if (err) return logger.error(err);

            logger.info('got notification that user logged in', sessionId, userId);

            var room = roomFor(userId);

            io.sockets.sockets.forEach(function(socket){
                if (socket.data.sessionId === sessionId) {
                    updateSocketUser(socket, userId);
                }
            });

        });

    } else if (action === 'del' || action === 'expired') {

        logger.info('session', sessionId, 'ended');

        // find any sockets with this session

        io.sockets.sockets.forEach(function(socket){
            if (socket.data.sessionId === sessionId) {
                //logger.info('connection', socket.id, 'session', sessionId, 'ended, disconnecting from user', socket.data.userId);
                updateSocketUser(socket, undefined);

            }
        });

    }
});

// listen!

io.listen(WEBSOCKET_PORT);
adminServer.listen(HTTP_ADMIN_PORT);

logger.info('websocket listening on', WEBSOCKET_PORT);
logger.info('http admin server listening on', HTTP_ADMIN_PORT);

// utility functions

function getUserIdFor(sessionId, callback) {
    redis.get(REDIS_SESSION_PREFIX + sessionId, function(err, userId){
        if (err) return callback(err);
        if (!userId) return callback(new Error('no session found for [' + sessionId + ']'));
        callback(null, userId);
    });
}

function roomFor(userId) {
    return 'room:' + userId;
}
<commit_msg>Will unset sessionId on connection when needed<commit_after>var argv = require('yargs')
    .usage('Usage: $0 -w [websocket port] -p [http admin port]')
    .demand(['w','p'])
    .describe('w', 'Websocket port')
    .describe('p', 'HTTP admin port')
    .argv;

var REDIS_NOTIFICATION_CHANNEL = 'notifications';
var REDIS_SESSION_PREFIX = 'session-store-';
var REDIS_KEYSPACE_CHANNEL = '__keyspace@0__:' + REDIS_SESSION_PREFIX + '*';
var WEBSOCKET_PORT = argv.w;
var HTTP_ADMIN_PORT = argv.p;
var SOCKETIO_CHANNEL = 'message';

var io = require('socket.io')({
    path: '/socket',
    serveClient: false
});

var adminServer = require('./admin-server')(io);

var redis = require('redis').createClient();
var redisSubscriber = require('redis').createClient();

redisSubscriber.config('SET', 'notify-keyspace-events', 'KA');

var winston = require('winston');
var logger = new winston.Logger({
    transports: [
      new (winston.transports.Console)({
        timestamp: function() {
          return new Date().toISOString();
        }
      })
    ]
});

logger.info('starting');

/*

    incoming websocket connections arrive here

    the client needs to send an "authenticate" message with:

        {
            "sessionId" : "<some session id>"
        }

    we check redis to see if we have a session/userid registered
    for this. otherwise we just leave the connection open, and hook it
    up later if we get notified from redis.

*/

io.on('connection', function(socket) {

    socket.data = {};

    logger.info('connection', socket.id, 'connected', socket.handshake);

    // when they emit an authenticate message, check they are ok

    socket.on('authenticate', function(data) {

        logger.info('connection', socket.id, 'authenticating', data);

        socket.data.sessionId = data.sessionId;

        getUserIdFor(data.sessionId, function(err, userId) {
            if (err) {
                logger.info('connection', socket.id, 'session', data.sessionId, 'has no logged-in user');
                return;
            }
            updateSocketUser(socket, userId);
        });

    });

    socket.on('disconnect', function() {
        logger.info('connection', socket.id, 'disconnected');
        logger.info('connection count', io.sockets.sockets.length);
    });

});

function updateSocketUser(socket, userId) {

    if (userId && socket.data.userId !== userId) {
        var joinRoom = roomFor(userId);
        logger.info('connection', socket.id, 'joining [' + joinRoom + ']');
        socket.join(joinRoom);
    }

    if (socket.data.userId !== undefined &&
        socket.data.userId !== userId) {
        var leaveRoom = roomFor(socket.data.userId);
        logger.info('connection', socket.id, 'leaving [' + leaveRoom + ']');
        socket.leave(leaveRoom);
    }

    socket.data.userId = userId;
}

/*

    subscribe redis to listen for messages on the notification channel

    these will then be pushed to the relevent users

    the content is a json object like:

        {
            "users": [43, 35, 35],
            "type": "some_message_type",
            "payload": {
                "stuff" : "for",
                "the"   : "users"
            }
        }

*/

redisSubscriber.subscribe(REDIS_NOTIFICATION_CHANNEL);
logger.info('subscribed to redis channel', REDIS_NOTIFICATION_CHANNEL);

redisSubscriber.on('message', function(channel, str) {
    if (channel === REDIS_NOTIFICATION_CHANNEL) {
        try {
            var message = JSON.parse(str);
            if (message.users === undefined || message.users.constructor !== Array) {
                logger.info('discarding message as it does not specify a user array', message);
                return;
            } else if (message.payload === undefined) {
                logger.info('discarding message as it does not have a payload', message);
                return;
            } else {
                var users = message.users;
                delete message.users; // the remaining fields go to each user
                logger.info('publishing [', message, ']', 'to users', users.join(','));
                users.forEach(function(userId){
                    console.log('publishing to', roomFor(userId));
                    io.to(roomFor(userId)).emit(SOCKETIO_CHANNEL, message);
                });
            }
        } catch (e) {
            logger.error('failed to parsed json from redis [' + str + ']', e.message);
        }
    } else {
        logger.info('got message on unknown channel', channel, str);
    }
});

/*

    we listen for key events on redis so that we can:

        1) know when when a session/userid pair is added to redis
        2) know when a session is removed from redis

*/

redisSubscriber.psubscribe(REDIS_KEYSPACE_CHANNEL);
logger.info('subscribed to redis channel', REDIS_KEYSPACE_CHANNEL);

redisSubscriber.on('pmessage', function(pattern, channel, action){
    var m = new RegExp(':' + REDIS_SESSION_PREFIX + '(.*)$').exec(channel);
    if (!m) return;
    var sessionId = m[1];
    if (action === 'set') {

        getUserIdFor(sessionId, function(err, userId){
            if (err) return logger.error(err);

            logger.info('got notification that user logged in', sessionId, userId);

            var room = roomFor(userId);

            io.sockets.sockets.forEach(function(socket){
                if (socket.data.sessionId === sessionId) {
                    updateSocketUser(socket, userId);
                }
            });

        });

    } else if (action === 'del' || action === 'expired') {

        logger.info('session', sessionId, 'ended');

        // find any sockets with this session

        io.sockets.sockets.forEach(function(socket){
            if (socket.data.sessionId === sessionId) {
                //logger.info('connection', socket.id, 'session', sessionId, 'ended, disconnecting from user', socket.data.userId);
                updateSocketUser(socket, undefined);
                socket.data.sessionId = undefined;
            }
        });

    }
});

// listen!

io.listen(WEBSOCKET_PORT);
adminServer.listen(HTTP_ADMIN_PORT);

logger.info('websocket listening on', WEBSOCKET_PORT);
logger.info('http admin server listening on', HTTP_ADMIN_PORT);

// utility functions

function getUserIdFor(sessionId, callback) {
    redis.get(REDIS_SESSION_PREFIX + sessionId, function(err, userId){
        if (err) return callback(err);
        if (!userId) return callback(new Error('no session found for [' + sessionId + ']'));
        callback(null, userId);
    });
}

function roomFor(userId) {
    return 'room:' + userId;
}
<|endoftext|>


<commit_before>var mongoose     = require('mongoose');
var Schema       = mongoose.Schema;

var ObjectId = mongoose.Schema.Types.ObjectId;
var Currency = require('./currency_model');
var User = require("./user_model");
var Organisation = require("./organisation_model");
var moment = require("moment");

var WalletSchema   = new Schema({
	name: { type: String, required: true, validate: /\S+/ },
	currency_id: { type: ObjectId, index: true, ref: "Currency", required: true },
	priority: { type: Number, required: true },
	quota_frequency: { type: String, validate: /daily|weekly|monthly|annually|never/, index: true, default: "never" },
	quota_amount: { type: Number, default: 0 },
	last_quota_date: { type: Date, index: true, default: Date.now },
	user_id: [{ type: ObjectId, index: true, ref: "User", required: true }],
	organisation_id: { type: ObjectId, index: true, ref: "Organisation" },
	balance: { type: Number, default: 0 },
	date_created: { type: Date, default: Date.now },
	_owner_id: ObjectId
});

WalletSchema.set("_perms", {
	admin: "crud",
	user: "r",
});

WalletSchema.statics.topup_daily = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "day");
	return Wallet.find({ quota_frequency: "daily", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

WalletSchema.statics.topup_weekly = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "week");
	return Wallet.find({ quota_frequency: "weekly", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

WalletSchema.statics.topup_monthly = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "month");
	return Wallet.find({ quota_frequency: "monthly", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

WalletSchema.statics.topup_annually = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "year");
	return Wallet.find({ quota_frequency: "annually", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

module.exports = mongoose.model('Wallet', WalletSchema);<commit_msg>Personal wallets<commit_after>var mongoose     = require('mongoose');
var Schema       = mongoose.Schema;
var moment = require("moment");
var async = require("async");
var ObjectId = mongoose.Schema.Types.ObjectId;
var Currency = require('./currency_model');
var User = require("./user_model");
var Organisation = require("./organisation_model");

var WalletSchema   = new Schema({
	name: { type: String, required: true, validate: /\S+/ },
	currency_id: { type: ObjectId, index: true, ref: "Currency", required: true },
	priority: { type: Number, required: true },
	quota_frequency: { type: String, validate: /daily|weekly|monthly|annually|never/, index: true, default: "never" },
	quota_amount: { type: Number, default: 0 },
	last_quota_date: { type: Date, index: true, default: Date.now },
	user_id: [{ type: ObjectId, index: true, ref: "User", required: true }],
	organisation_id: { type: ObjectId, index: true, ref: "Organisation" },
	balance: { type: Number, default: 0 },
	date_created: { type: Date, default: Date.now },
	personal: Boolean,
	_owner_id: ObjectId
});

WalletSchema.set("_perms", {
	admin: "crud",
	user: "r",
});

WalletSchema.index({ name: 1, user_id: 1 }, { unique: true });

WalletSchema.statics.topup_daily = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "day");
	return Wallet.find({ quota_frequency: "daily", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

WalletSchema.statics.topup_weekly = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "week");
	return Wallet.find({ quota_frequency: "weekly", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

WalletSchema.statics.topup_monthly = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "month");
	return Wallet.find({ quota_frequency: "monthly", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

WalletSchema.statics.topup_annually = function() {
	var Wallet = require("./wallet_model");
	var day_ago = moment().subtract(1, "year");
	return Wallet.find({ quota_frequency: "annually", last_quota_date: { $lte: day_ago } })
	.then(wallets => {
		var result = [];
		wallets.forEach(wallet => {
			wallet.balance = wallet.quota_amount;
			wallet.last_quota_date = new Date();
			wallet.save();
			result.push(wallet);
		});
		return result;
	});
};

// Set this wallet as the Personal wallet and unset any other wallets for the same currency and user
WalletSchema.statics.set_personal = function(_id) {
	var Wallet = require("./wallet_model");
	var queue = [];
	var wallet = null;
	return Wallet.findOne({ _id })
	.then(result => {
		wallet = result;
		return Wallet.find({ currency: wallet.currency, user_id: wallet.user_id, personal: true });
	})
	.then(wallets => {
		wallets.forEach(w => {
			if (w._id.toString() !== wallet._id.toString()) {
				console.log(w._id, wallet._id);
				queue.push(cb => {
					w.personal = false;
					w.save()
					.then(result => {
						cb(null, result);
					}, err => {
						console.error(err);
						cb(err);
					});
				});
			}
		});
		queue.push(cb => {
			wallet.personal = true;
			wallet.save()
			.then(result => {
				cb(null, result);
			}, err => {
				console.error(err);
				cb(err);
			});
		});
		return new Promise((resolve, reject) => {
			async.series(queue, (err, result) => {
				if (err)
					return reject(err);
				return resolve(result);
			});
		});
	});
};

module.exports = mongoose.model('Wallet', WalletSchema);<|endoftext|>


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

var fs = require('fs');
var path = require('path');
var util = require('util');

var _ = require('underscore');
var async = require('async');
var sqlite3 = require('sqlite3').verbose();

var settings = require('../settings');
var utils = require('../utils');
var events = require('./events');

var db;

var SCHEMA = ["CREATE TABLE IF NOT EXISTS events (project VARCHAR(40) NOT NULL, " +
  "username varchar(40) NOT NULL, " +
  "time TIMESTAMP NOT NULL, " +
  "points INTEGER NOT NULL, " +
  "related_api VARCHAR(40) NOT NULL, " +
  "details TEXT, " +
  "story_points INTEGER, " +
  "multiplier INTEGER, " +
  "url TEXT, " +
  "title TEXT, " +
  "id INTEGER PRIMARY KEY);",

  "CREATE TABLE IF NOT EXISTS metrics (project VARCHAR(40) NOT NULL," +
  "service_name VARCHAR(40) PRIMARY KEY, " +
  "time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "+
  "health TINYINT NOT NULL, " +
  "ip VARCHAR(20) NOT NULL, " +
  "services TEXT NOT NULL);",

  "CREATE TABLE IF NOT EXISTS alias_display (id INTEGER PRIMARY KEY, " +
  "display_name VARCHAR(40) NOT NULL, UNIQUE(display_name));",

  "CREATE TABLE IF NOT EXISTS aliases (alias_display_id REFERENCES alias_display(id) NOT NULL," +
  "alias TEXT NOT NULL, related_api VARCHAR(40) NOT NULL, UNIQUE(alias, related_api), UNIQUE(alias, alias_display_id), UNIQUE(related_api, alias_display_id));"
];
// TODO: back up aliases before changing schemas

// a wrapper (ORM le sigh) for interacting with sqlite3
module.exports = new utils.make_class({
  init: function(){
    var self = this;
    var _path = path.join(__dirname, '..', 'db.sqlite');
    self.db = new sqlite3.Database(_path);
    if (settings.debug === true) {
      self.db.on('trace', function(event){
        console.log(event.toString());
      });
    }
  },
  create_tables: function(cb){
    var self = this;
    var tables = [];
    _.each(SCHEMA, function(create_table){
      tables.push(_.bind(self.db.run, self.db, create_table));
    });
    async.series(tables, cb);
  },
  get_last_v1_time: function(project, cb){
    var self = this;

    self.db.get("SELECT max(time) as date FROM events where project=? and related_api=?;",
      [project, events.RELATED_APIS.version_one],
      function(err, results){
        return cb(err, new Date(results ? results.date : null));
      });
  },
  add_event: function(event, project, cb){
    var self = this;
    var details;
    var score;

    //console.log("adding event");
    //console.log(event);
    if (!event.user){
      console.log("No user for event. Not inserting.", event);
      return null;
    }

    details = JSON.stringify(event.details);
    //console.log(details);
    self.db.run("INSERT INTO events(project, username, time, points, related_api, details, story_points, multiplier, url, title) values (?, ?, ?, ?, ?, ?, ?, ?, ? , ?);",
      [project, event.user, event.date, event.get_points(), event.related_api, details, event.story_points, event.multiplier, event.url, event.title],
      cb);
  },
  // TODO: this is kinda stupid, figure out how to do it the right way
  add_multiple_events: function(events, project, cb){
    var self = this;
    var values = [];

    _.each(events, function(event){
      self.add_event(event, project);
    });
  },
  get_highscores: function(project, start, end, cb){
    var self = this;
    var query = "SELECT COALESCE((SELECT display_name from alias_display where id=aliases.alias_display_id), username) as name, username, sum(points) as score from events " +
    "LEFT OUTER JOIN aliases on username=alias and events.related_api=aliases.related_api ";
    var args = [];
    var where = "";
    if (start){
      start = Date.parse(start);
      where += " time>=? AND ";
      args.push(start);
    }
    if (end){
      end = Date.parse(end);
      where += ' time<? AND ';
      args.push(end);
    }
    if (project){
      where += " project=? ";
      args.push(project);
    }

    if (where.length > 0){
      query += " where " + where;
    }

    query += " group by name order by score desc;";

    self.db.all(query, args, cb);
  },
  get_highscore_breakdown: function(project, start, end, username, cb){
    var self = this;
    var query = "SELECT COALESCE((SELECT display_name from alias_display where id=aliases.alias_display_id), username) as name, username, time, points, events.related_api, " +
    "story_points, multiplier, url, title, id from events outer left join aliases on username=alias " +
    "and events.related_api=aliases.related_api ";
    var args = [];
    var where = "";
    if (start){
      where += " time>=? AND ";
      args.push(Date.parse(start));
    }
    if (end){
      where += " time<? AND ";
      args.push(Date.parse(end));
    }
    if (username) {
      where += " name=? AND ";
      args.push(username);
    }
    if (project){
      where += " project=? ";
      args.push(project);
    }

    if (where.length > 0){
      query += " where " + where;
    }

    query += " order by time desc;";

    self.db.all(query, args, cb);
  },
  get_aliases: function(display_name, cb){
    var self = this;
    self.db.all("SELECT * FROM aliases WHERE alias_display_id=(SELECT id FROM alias_display WHERE display_name=?);", display_name, cb);
  },
  get_db: function () {
    var self = this;
    return self.db;
  },
  add_alias: function(alias, display_name, related_api, alias_display_id, cb){
    var self = this;
    var db_ops = [];

    if (!events.RELATED_APIS[related_api]){
      return cb(new Error('use the enum'+related_api));
    }

    if (!display_name || display_name.length <= 0) {
      return cb(new Error("No display name!"));
    }

    if (alias_display_id) {
      db_ops.push(function (cb) {
        self.db.run("UPDATE alias_display SET display_name = ? WHERE id = ?", [display_name, alias_display_id], cb);
      });
    }
    else {
      db_ops.push(function (cb) {
        self.db.run("INSERT INTO alias_display (display_name) VALUES (?);",
          [display_name], cb);
      });
    }

    db_ops.push(function (cb) {
      self.db.run("INSERT OR REPLACE INTO aliases(alias, related_api, alias_display_id) VALUES(?, ?, COALESCE(?, (SELECT id FROM alias_display WHERE alias_display.display_name=?)));",
      [alias, related_api, alias_display_id || null, display_name], cb);
    });

    async.series(db_ops, cb);
  },
  delete_alias: function(related_api, alias_display_id, cb){
    var self = this;
    self.db.run("DELETE FROM aliases WHERE alias_display_id =? AND related_api = ?;",
      alias_display_id, related_api, cb
    );
  },
  add_service_stat: function(project, ip, host_data, cb){
    var self = this;
    self.db.run("INSERT OR REPLACE INTO metrics (project, service_name, health, ip, time, services) " +
      "VALUES (?, ?, ?, ?, ?, ?);", [ project, host_data.name, host_data.health, ip,
      host_data.timestamp, JSON.stringify(host_data.services)], function(err, results){
        if (err){
          console.log(err);
        }
        if (cb){
          cb(err, results);
        }
    });
  },
  get_service_stats: function(project, cb){
    var self = this;
    return self.db.all("SELECT * FROM metrics where project=?;", project, function(err, results){
      _.each(results, function(row){
        row.services = JSON.parse(row.services);
      });
      cb(err, results);
    });
  },
  has_service_stats: function(project, cb){
    var self = this;
    return self.db.get("SELECT count(1) > 0 AS has_metric FROM metrics WHERE project=? LIMIT 1;", project, function(err, result){
      if (result.has_metric > 0) {
        return cb(err, true);
      }
      return cb(err, false);
    });
  }
})();
<commit_msg>Probably want to run callback here.<commit_after>/*
 *  Copyright 2011 Rackspace
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var fs = require('fs');
var path = require('path');
var util = require('util');

var _ = require('underscore');
var async = require('async');
var sqlite3 = require('sqlite3').verbose();

var settings = require('../settings');
var utils = require('../utils');
var events = require('./events');

var db;

var SCHEMA = ["CREATE TABLE IF NOT EXISTS events (project VARCHAR(40) NOT NULL, " +
  "username varchar(40) NOT NULL, " +
  "time TIMESTAMP NOT NULL, " +
  "points INTEGER NOT NULL, " +
  "related_api VARCHAR(40) NOT NULL, " +
  "details TEXT, " +
  "story_points INTEGER, " +
  "multiplier INTEGER, " +
  "url TEXT, " +
  "title TEXT, " +
  "id INTEGER PRIMARY KEY);",

  "CREATE TABLE IF NOT EXISTS metrics (project VARCHAR(40) NOT NULL," +
  "service_name VARCHAR(40) PRIMARY KEY, " +
  "time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "+
  "health TINYINT NOT NULL, " +
  "ip VARCHAR(20) NOT NULL, " +
  "services TEXT NOT NULL);",

  "CREATE TABLE IF NOT EXISTS alias_display (id INTEGER PRIMARY KEY, " +
  "display_name VARCHAR(40) NOT NULL, UNIQUE(display_name));",

  "CREATE TABLE IF NOT EXISTS aliases (alias_display_id REFERENCES alias_display(id) NOT NULL," +
  "alias TEXT NOT NULL, related_api VARCHAR(40) NOT NULL, UNIQUE(alias, related_api), UNIQUE(alias, alias_display_id), UNIQUE(related_api, alias_display_id));"
];
// TODO: back up aliases before changing schemas

// a wrapper (ORM le sigh) for interacting with sqlite3
module.exports = new utils.make_class({
  init: function(){
    var self = this;
    var _path = path.join(__dirname, '..', 'db.sqlite');
    self.db = new sqlite3.Database(_path);
    if (settings.debug === true) {
      self.db.on('trace', function(event){
        console.log(event.toString());
      });
    }
  },
  create_tables: function(cb){
    var self = this;
    var tables = [];
    _.each(SCHEMA, function(create_table){
      tables.push(_.bind(self.db.run, self.db, create_table));
    });
    async.series(tables, cb);
  },
  get_last_v1_time: function(project, cb){
    var self = this;

    self.db.get("SELECT max(time) as date FROM events where project=? and related_api=?;",
      [project, events.RELATED_APIS.version_one],
      function(err, results){
        return cb(err, new Date(results ? results.date : null));
      });
  },
  add_event: function(event, project, cb){
    var self = this;
    var details;
    var score;

    //console.log("adding event");
    //console.log(event);
    if (!event.user){
      console.log("No user for event. Not inserting.", event);
      return cb();
    }

    details = JSON.stringify(event.details);
    //console.log(details);
    self.db.run("INSERT INTO events(project, username, time, points, related_api, details, story_points, multiplier, url, title) values (?, ?, ?, ?, ?, ?, ?, ?, ? , ?);",
      [project, event.user, event.date, event.get_points(), event.related_api, details, event.story_points, event.multiplier, event.url, event.title],
      cb);
  },
  // TODO: this is kinda stupid, figure out how to do it the right way
  add_multiple_events: function(events, project, cb){
    var self = this;
    var values = [];

    _.each(events, function(event){
      self.add_event(event, project);
    });
  },
  get_highscores: function(project, start, end, cb){
    var self = this;
    var query = "SELECT COALESCE((SELECT display_name from alias_display where id=aliases.alias_display_id), username) as name, username, sum(points) as score from events " +
    "LEFT OUTER JOIN aliases on username=alias and events.related_api=aliases.related_api ";
    var args = [];
    var where = "";
    if (start){
      start = Date.parse(start);
      where += " time>=? AND ";
      args.push(start);
    }
    if (end){
      end = Date.parse(end);
      where += ' time<? AND ';
      args.push(end);
    }
    if (project){
      where += " project=? ";
      args.push(project);
    }

    if (where.length > 0){
      query += " where " + where;
    }

    query += " group by name order by score desc;";

    self.db.all(query, args, cb);
  },
  get_highscore_breakdown: function(project, start, end, username, cb){
    var self = this;
    var query = "SELECT COALESCE((SELECT display_name from alias_display where id=aliases.alias_display_id), username) as name, username, time, points, events.related_api, " +
    "story_points, multiplier, url, title, id from events outer left join aliases on username=alias " +
    "and events.related_api=aliases.related_api ";
    var args = [];
    var where = "";
    if (start){
      where += " time>=? AND ";
      args.push(Date.parse(start));
    }
    if (end){
      where += " time<? AND ";
      args.push(Date.parse(end));
    }
    if (username) {
      where += " name=? AND ";
      args.push(username);
    }
    if (project){
      where += " project=? ";
      args.push(project);
    }

    if (where.length > 0){
      query += " where " + where;
    }

    query += " order by time desc;";

    self.db.all(query, args, cb);
  },
  get_aliases: function(display_name, cb){
    var self = this;
    self.db.all("SELECT * FROM aliases WHERE alias_display_id=(SELECT id FROM alias_display WHERE display_name=?);", display_name, cb);
  },
  get_db: function () {
    var self = this;
    return self.db;
  },
  add_alias: function(alias, display_name, related_api, alias_display_id, cb){
    var self = this;
    var db_ops = [];

    if (!events.RELATED_APIS[related_api]){
      return cb(new Error('use the enum'+related_api));
    }

    if (!display_name || display_name.length <= 0) {
      return cb(new Error("No display name!"));
    }

    if (alias_display_id) {
      db_ops.push(function (cb) {
        self.db.run("UPDATE alias_display SET display_name = ? WHERE id = ?", [display_name, alias_display_id], cb);
      });
    }
    else {
      db_ops.push(function (cb) {
        self.db.run("INSERT INTO alias_display (display_name) VALUES (?);",
          [display_name], cb);
      });
    }

    db_ops.push(function (cb) {
      self.db.run("INSERT OR REPLACE INTO aliases(alias, related_api, alias_display_id) VALUES(?, ?, COALESCE(?, (SELECT id FROM alias_display WHERE alias_display.display_name=?)));",
      [alias, related_api, alias_display_id || null, display_name], cb);
    });

    async.series(db_ops, cb);
  },
  delete_alias: function(related_api, alias_display_id, cb){
    var self = this;
    self.db.run("DELETE FROM aliases WHERE alias_display_id =? AND related_api = ?;",
      alias_display_id, related_api, cb
    );
  },
  add_service_stat: function(project, ip, host_data, cb){
    var self = this;
    self.db.run("INSERT OR REPLACE INTO metrics (project, service_name, health, ip, time, services) " +
      "VALUES (?, ?, ?, ?, ?, ?);", [ project, host_data.name, host_data.health, ip,
      host_data.timestamp, JSON.stringify(host_data.services)], function(err, results){
        if (err){
          console.log(err);
        }
        if (cb){
          cb(err, results);
        }
    });
  },
  get_service_stats: function(project, cb){
    var self = this;
    return self.db.all("SELECT * FROM metrics where project=?;", project, function(err, results){
      _.each(results, function(row){
        row.services = JSON.parse(row.services);
      });
      cb(err, results);
    });
  },
  has_service_stats: function(project, cb){
    var self = this;
    return self.db.get("SELECT count(1) > 0 AS has_metric FROM metrics WHERE project=? LIMIT 1;", project, function(err, result){
      if (result.has_metric > 0) {
        return cb(err, true);
      }
      return cb(err, false);
    });
  }
})();
<|endoftext|>