<commit_before>if (typeof Prototype == 'undefined')
{
  warning = "ActiveScaffold Error: Prototype could not be found. Please make sure that your application's layout includes prototype.js (e.g. <%= javascript_include_tag :defaults %>) *before* it includes active_scaffold.js (e.g. <%= active_scaffold_includes %>).";
  alert(warning);
}

/*
 * Simple utility methods
 */

var ActiveScaffold = {
  stripe: function(tableBody) {
    var even = false;
    var tableBody = $(tableBody);
    var tableRows = tableBody.getElementsByTagName("tr");
    var length = tableBody.rows.length;

    for (var i = 0; i < length; i++) {
      var tableRow = tableBody.rows[i];
      //Make sure to skip rows that are create or edit rows or messages
      if (!tableRow.hasClassName("create")
        && !tableRow.hasClassName("update")
        && !tableRow.hasClassName("inline-adapter")
        && !tableRow.hasClassName("active-scaffold-calculations")) {

        if (even) {
          tableRow.addClassName("even");
        } else {
          tableRow.removeClassName("even");
        }
        even = !even;
      }
    }
  },
  toggleEmptyMessage: function(tableBody, emptyMessageElement) {
    // Check to see if this was the last element in the list
    if ($(tableBody).rows.length == 0) {
      $(emptyMessageElement).show();
    } else {
      $(emptyMessageElement).hide();
    }
  },
  removeSortClasses: function(active_scaffoldId) {
    $$('#' + active_scaffoldId + ' td.sorted').each(function(element) {
      element.removeClassName("sorted");
    });
    $$('#' + active_scaffoldId + ' th.sorted').each(function(element) {
      element.removeClassName("sorted");
      element.removeClassName("asc");
      element.removeClassName("desc");
    });
  },
  decrement_record_count: function(active_scaffoldId) {
    count = $$('#' + active_scaffoldId + ' span.active-scaffold-records').first();
    count.innerHTML = parseInt(count.innerHTML) - 1;
  },
  increment_record_count: function(active_scaffoldId) {
    count = $$('#' + active_scaffoldId + ' span.active-scaffold-records').first();
    count.innerHTML = parseInt(count.innerHTML) + 1;
  }
}

/*
 * Add-ons/Patches to Prototype
 */

/* patch to support replacing TR/TD/TBODY in Internet Explorer, courtesy of http://dev.rubyonrails.org/ticket/4273 */
Element.replace = function(element, html) {
  element = $(element);
  if (element.outerHTML) {
    try {
    element.outerHTML = html.stripScripts();
    } catch (e) {
      var tn = element.tagName;
      if(tn=='TBODY' || tn=='TR' || tn=='TD')
      {
              var tempDiv = document.createElement("div");
              tempDiv.innerHTML = '
' + html.stripScripts() + '
';
              element.parentNode.replaceChild(tempDiv.getElementsByTagName(tn).item(0), element);
      }
      else throw e;
    }
  } else {
    var range = element.ownerDocument.createRange();
    range.selectNodeContents(element);
    element.parentNode.replaceChild(range.createContextualFragment(html.stripScripts()), element);
  }
  setTimeout(function() {html.evalScripts()}, 10);
};

/*
 * URL modification support. Incomplete functionality.
 */
Object.extend(String.prototype, {
  append_params: function(params) {
    url = this;
    if (url.indexOf('?') == -1) url += '?';
    else if (url.indexOf('&') != url.length) url += '&';

    url += $H(params).collect(function(item) {
      return item.key + '=' + item.value;
    }).join('&');

    return url;
  }
});

/*
 * Nested Form... sorta
 */
Form.Pseudo = Class.create();
Form.Pseudo.prototype = {
  initialize: function(element, options) {
    this.element = $(element);
    this.href = $$("#" + this.element.id + " .form-action")[0].href;
    this.setOptions(options);

    // Put hook on buttons
    this.submitButtons = $$("#" + this.element.id + " .submit");
    for (var i=0; i < this.submitButtons.length; i++) {
      Event.observe(this.submitButtons[i], 'click', this.onSubmit.bindAsEventListener(this));
    }

    // find action uri for request
    this.formElements = Form.getElements(this.element);
    for (var i=0; i < this.formElements.length; i++) {
      Event.observe(this.formElements[i], 'keydown', this.onKeyPress.bindAsEventListener(this));
    }
  },

  setOptions: function(options) {
  this.options = { asynchronous: true,
                     evalScripts: true,
                     onLoading: this.onLoading.bindAsEventListener(this),
                     onLoaded: this.onComplete.bindAsEventListener(this) };
    Object.extend(this.options, options || {});
  },

  onKeyPress: function(event) {
    if (event.keyCode == Event.KEY_RETURN) {
      this.onSubmit(event);
    }
  },

  onLoading: function(request) {
    Form.disable(this.element);
  },

  onComplete: function(request) {
    Form.enable(this.element);
  },

  onSubmit: function(event) {
    var params = Object.extend(this.options, { parameters: Form.serialize(this.element) });
    new Ajax.Request(this.href, params);
    Event.stop(event);
  }
}

var PsuedoForm = {
  clear: function(element) {
    this.element = $(element);

    this.formElements = Form.getElements(this.element);
    for (var i=0; i < this.formElements.length; i++) {
      if (this.formElements[i].type.toLowerCase() != 'submit') {
        this.formElements[i].value = "";
      }
    }
  }
}

/**
 * A set of links. As a set, they can be controlled such that only one is "open" at a time, etc.
 */
ActiveScaffold.Actions = new Object();
ActiveScaffold.Actions.Abstract = function(){}
ActiveScaffold.Actions.Abstract.prototype = {
  initialize: function(links, target, loading_indicator, options) {
    this.target = $(target);
    this.loading_indicator = $(loading_indicator);
    this.options = options;
    this.links = links.collect(function(link) {
      return this.instantiate_link(link);
    }.bind(this));
  },

  instantiate_link: function(link) {
    throw 'unimplemented'
  }
}

/**
 * A DataStructures::ActionLink, represented in JavaScript.
 * Concerned with AJAX-enabling a link and adapting the result for insertion into the table.
 */
ActiveScaffold.ActionLink = new Object();
ActiveScaffold.ActionLink.Abstract = function(){}
ActiveScaffold.ActionLink.Abstract.prototype = {
  initialize: function(a, target, loading_indicator) {
    this.tag = $(a);
    this.url = this.tag.href;
    this.target = target;
    this.loading_indicator = loading_indicator;
    this.hide_target = false;
    this.position = this.tag.getAttribute('position');

    this.onclick = this.tag.onclick;
    this.tag.onclick = null;
    this.tag.observe('click', function(event) {
      this.open();
      Event.stop(event);
    }.bind(this));

    this.tag.action_link = this;
  },

  open: function() {
    if (this.is_disabled()) return;
    if (this.onclick && !this.onclick()) return;//e.g. confirmation messages
    if (this.position) this.disable();
    this.loading_indicator.style.visibility = 'visible';
    new Ajax.Request(this.url, {
      asynchronous: true,
      evalScripts: true,

      onSuccess: function(request) {
        if (this.position) {
          this.insert(request.responseText);
          if (this.hide_target) this.target.hide();
        } else {
          request.evalResponse();
        }
      }.bind(this),

      onFailure: function(request) {
        if (this.position) this.enable()
      }.bind(this),

      onComplete: function(request) {
        this.loading_indicator.style.visibility = 'hidden';
      }.bind(this)
    });
  },

  insert: function(content) {
    throw 'unimplemented'
  },

  close: function() {
    this.enable();
    this.adapter.remove();
    if (this.hide_target) this.target.show();
  },

  get_new_adapter_id: function() {
    var id = 'adapter_';
    var i = 0;
    while ($(id + i)) i++;
    return id + i;
  },

  enable: function() {
    return this.tag.removeClassName('disabled');
  },

  disable: function() {
    return this.tag.addClassName('disabled');
  },

  is_disabled: function() {
    return this.tag.hasClassName('disabled');
  }
}

/**
 * Concrete classes for record actions
 */
ActiveScaffold.Actions.Record = Class.create();
ActiveScaffold.Actions.Record.prototype = Object.extend(new ActiveScaffold.Actions.Abstract(), {
  instantiate_link: function(link) {
    var l = new ActiveScaffold.ActionLink.Record(link, this.target, this.loading_indicator);
    l.refresh_url = this.options.refresh_url;
    if (l.position) l.url = l.url.append_params({adapter: '_list_inline_adapter'});
    l.set = this;
    return l;
  }
});

ActiveScaffold.ActionLink.Record = Class.create();
ActiveScaffold.ActionLink.Record.prototype = Object.extend(new ActiveScaffold.ActionLink.Abstract(), {
  insert: function(content) {
    this.set.links.each(function(item) {
      if (item.url != this.url && item.is_disabled() && item.adapter) item.close();
    }.bind(this));

    if (this.position == 'replace') {
      this.position = 'after';
      this.hide_target = true;
    }

    if (this.position == 'after') {
      new Insertion.After(this.target, content);
      this.adapter = this.target.next();
    }
    else if (this.position == 'before') {
      new Insertion.Before(this.target, content);
      this.adapter = this.target.previous();
    }
    else {
      return false;
    }

    this.adapter.down('a.inline-adapter-close').observe('click', function(event) {
      this.close_with_refresh();
      Event.stop(event);
    }.bind(this));

    new Effect.Highlight(this.adapter.down('td'));
  },

  close_with_refresh: function() {
    new Ajax.Request(this.refresh_url, {
      asynchronous: true,
      evalScripts: true,

      onSuccess: function(request) {
        Element.replace(this.target, request.responseText);
        var new_target = $(this.target.id);
        if (this.target.hasClassName('even')) new_target.addClassName('even');
        this.target = new_target;
        this.close();
      }.bind(this)
    });
  },

  enable: function() {
    this.set.links.each(function(item) {
      if (item.url != this.url) return;
      item.tag.removeClassName('disabled');
    }.bind(this));
  },

  disable: function() {
    this.set.links.each(function(item) {
      if (item.url != this.url) return;
      item.tag.addClassName('disabled');
    }.bind(this));
  }
});

/**
 * Concrete classes for table actions
 */
ActiveScaffold.Actions.Table = Class.create();
ActiveScaffold.Actions.Table.prototype = Object.extend(new ActiveScaffold.Actions.Abstract(), {
  instantiate_link: function(link) {
    var l = new ActiveScaffold.ActionLink.Table(link, this.target, this.loading_indicator);
    l.url = l.url.append_params({adapter: '_list_inline_adapter'});
    return l;
  }
});

ActiveScaffold.ActionLink.Table = Class.create();
ActiveScaffold.ActionLink.Table.prototype = Object.extend(new ActiveScaffold.ActionLink.Abstract(), {
  insert: function(content) {
    if (this.position == 'top') {
      new Insertion.Top(this.target, content);
      this.adapter = this.target.immediateDescendants().first();
    }
    else {
      throw 'Unknown position "' + this.position + '"'
    }

    this.adapter.down('a.inline-adapter-close').observe('click', function(event) {
      this.close();
      Event.stop(event);
    }.bind(this));

    new Effect.Highlight(this.adapter.down('td'));
  }
});<commit_msg>ActiveScaffold does not support 1.5.0_rc version of prototype.js<commit_after>if (typeof Prototype == 'undefined')
{
  warning = "ActiveScaffold Error: Prototype could not be found. Please make sure that your application's layout includes prototype.js (e.g. <%= javascript_include_tag :defaults %>) *before* it includes active_scaffold.js (e.g. <%= active_scaffold_includes %>).";
  alert(warning);
}
if (Prototype.Version.substring(0, 8) == '1.5.0_rc')
{
  warning = "ActiveScaffold Error: Prototype 1.5.0_rc is not supported. Please update prototype.js (rake rails:update).";
  alert(warning);
}

/*
 * Simple utility methods
 */

var ActiveScaffold = {
  stripe: function(tableBody) {
    var even = false;
    var tableBody = $(tableBody);
    var tableRows = tableBody.getElementsByTagName("tr");
    var length = tableBody.rows.length;

    for (var i = 0; i < length; i++) {
      var tableRow = tableBody.rows[i];
      //Make sure to skip rows that are create or edit rows or messages
      if (!tableRow.hasClassName("create")
        && !tableRow.hasClassName("update")
        && !tableRow.hasClassName("inline-adapter")
        && !tableRow.hasClassName("active-scaffold-calculations")) {

        if (even) {
          tableRow.addClassName("even");
        } else {
          tableRow.removeClassName("even");
        }
        even = !even;
      }
    }
  },
  toggleEmptyMessage: function(tableBody, emptyMessageElement) {
    // Check to see if this was the last element in the list
    if ($(tableBody).rows.length == 0) {
      $(emptyMessageElement).show();
    } else {
      $(emptyMessageElement).hide();
    }
  },
  removeSortClasses: function(active_scaffoldId) {
    $$('#' + active_scaffoldId + ' td.sorted').each(function(element) {
      element.removeClassName("sorted");
    });
    $$('#' + active_scaffoldId + ' th.sorted').each(function(element) {
      element.removeClassName("sorted");
      element.removeClassName("asc");
      element.removeClassName("desc");
    });
  },
  decrement_record_count: function(active_scaffoldId) {
    count = $$('#' + active_scaffoldId + ' span.active-scaffold-records').first();
    count.innerHTML = parseInt(count.innerHTML) - 1;
  },
  increment_record_count: function(active_scaffoldId) {
    count = $$('#' + active_scaffoldId + ' span.active-scaffold-records').first();
    count.innerHTML = parseInt(count.innerHTML) + 1;
  }
}

/*
 * Add-ons/Patches to Prototype
 */

/* patch to support replacing TR/TD/TBODY in Internet Explorer, courtesy of http://dev.rubyonrails.org/ticket/4273 */
Element.replace = function(element, html) {
  element = $(element);
  if (element.outerHTML) {
    try {
    element.outerHTML = html.stripScripts();
    } catch (e) {
      var tn = element.tagName;
      if(tn=='TBODY' || tn=='TR' || tn=='TD')
      {
              var tempDiv = document.createElement("div");
              tempDiv.innerHTML = '
' + html.stripScripts() + '
';
              element.parentNode.replaceChild(tempDiv.getElementsByTagName(tn).item(0), element);
      }
      else throw e;
    }
  } else {
    var range = element.ownerDocument.createRange();
    range.selectNodeContents(element);
    element.parentNode.replaceChild(range.createContextualFragment(html.stripScripts()), element);
  }
  setTimeout(function() {html.evalScripts()}, 10);
};

/*
 * URL modification support. Incomplete functionality.
 */
Object.extend(String.prototype, {
  append_params: function(params) {
    url = this;
    if (url.indexOf('?') == -1) url += '?';
    else if (url.indexOf('&') != url.length) url += '&';

    url += $H(params).collect(function(item) {
      return item.key + '=' + item.value;
    }).join('&');

    return url;
  }
});

/*
 * Nested Form... sorta
 */
Form.Pseudo = Class.create();
Form.Pseudo.prototype = {
  initialize: function(element, options) {
    this.element = $(element);
    this.href = $$("#" + this.element.id + " .form-action")[0].href;
    this.setOptions(options);

    // Put hook on buttons
    this.submitButtons = $$("#" + this.element.id + " .submit");
    for (var i=0; i < this.submitButtons.length; i++) {
      Event.observe(this.submitButtons[i], 'click', this.onSubmit.bindAsEventListener(this));
    }

    // find action uri for request
    this.formElements = Form.getElements(this.element);
    for (var i=0; i < this.formElements.length; i++) {
      Event.observe(this.formElements[i], 'keydown', this.onKeyPress.bindAsEventListener(this));
    }
  },

  setOptions: function(options) {
  this.options = { asynchronous: true,
                     evalScripts: true,
                     onLoading: this.onLoading.bindAsEventListener(this),
                     onLoaded: this.onComplete.bindAsEventListener(this) };
    Object.extend(this.options, options || {});
  },

  onKeyPress: function(event) {
    if (event.keyCode == Event.KEY_RETURN) {
      this.onSubmit(event);
    }
  },

  onLoading: function(request) {
    Form.disable(this.element);
  },

  onComplete: function(request) {
    Form.enable(this.element);
  },

  onSubmit: function(event) {
    var params = Object.extend(this.options, { parameters: Form.serialize(this.element) });
    new Ajax.Request(this.href, params);
    Event.stop(event);
  }
}

var PsuedoForm = {
  clear: function(element) {
    this.element = $(element);

    this.formElements = Form.getElements(this.element);
    for (var i=0; i < this.formElements.length; i++) {
      if (this.formElements[i].type.toLowerCase() != 'submit') {
        this.formElements[i].value = "";
      }
    }
  }
}

/**
 * A set of links. As a set, they can be controlled such that only one is "open" at a time, etc.
 */
ActiveScaffold.Actions = new Object();
ActiveScaffold.Actions.Abstract = function(){}
ActiveScaffold.Actions.Abstract.prototype = {
  initialize: function(links, target, loading_indicator, options) {
    this.target = $(target);
    this.loading_indicator = $(loading_indicator);
    this.options = options;
    this.links = links.collect(function(link) {
      return this.instantiate_link(link);
    }.bind(this));
  },

  instantiate_link: function(link) {
    throw 'unimplemented'
  }
}

/**
 * A DataStructures::ActionLink, represented in JavaScript.
 * Concerned with AJAX-enabling a link and adapting the result for insertion into the table.
 */
ActiveScaffold.ActionLink = new Object();
ActiveScaffold.ActionLink.Abstract = function(){}
ActiveScaffold.ActionLink.Abstract.prototype = {
  initialize: function(a, target, loading_indicator) {
    this.tag = $(a);
    this.url = this.tag.href;
    this.target = target;
    this.loading_indicator = loading_indicator;
    this.hide_target = false;
    this.position = this.tag.getAttribute('position');

    this.onclick = this.tag.onclick;
    this.tag.onclick = null;
    this.tag.observe('click', function(event) {
      this.open();
      Event.stop(event);
    }.bind(this));

    this.tag.action_link = this;
  },

  open: function() {
    if (this.is_disabled()) return;
    if (this.onclick && !this.onclick()) return;//e.g. confirmation messages
    if (this.position) this.disable();
    this.loading_indicator.style.visibility = 'visible';
    new Ajax.Request(this.url, {
      asynchronous: true,
      evalScripts: true,

      onSuccess: function(request) {
        if (this.position) {
          this.insert(request.responseText);
          if (this.hide_target) this.target.hide();
        } else {
          request.evalResponse();
        }
      }.bind(this),

      onFailure: function(request) {
        if (this.position) this.enable()
      }.bind(this),

      onComplete: function(request) {
        this.loading_indicator.style.visibility = 'hidden';
      }.bind(this)
    });
  },

  insert: function(content) {
    throw 'unimplemented'
  },

  close: function() {
    this.enable();
    this.adapter.remove();
    if (this.hide_target) this.target.show();
  },

  get_new_adapter_id: function() {
    var id = 'adapter_';
    var i = 0;
    while ($(id + i)) i++;
    return id + i;
  },

  enable: function() {
    return this.tag.removeClassName('disabled');
  },

  disable: function() {
    return this.tag.addClassName('disabled');
  },

  is_disabled: function() {
    return this.tag.hasClassName('disabled');
  }
}

/**
 * Concrete classes for record actions
 */
ActiveScaffold.Actions.Record = Class.create();
ActiveScaffold.Actions.Record.prototype = Object.extend(new ActiveScaffold.Actions.Abstract(), {
  instantiate_link: function(link) {
    var l = new ActiveScaffold.ActionLink.Record(link, this.target, this.loading_indicator);
    l.refresh_url = this.options.refresh_url;
    if (l.position) l.url = l.url.append_params({adapter: '_list_inline_adapter'});
    l.set = this;
    return l;
  }
});

ActiveScaffold.ActionLink.Record = Class.create();
ActiveScaffold.ActionLink.Record.prototype = Object.extend(new ActiveScaffold.ActionLink.Abstract(), {
  insert: function(content) {
    this.set.links.each(function(item) {
      if (item.url != this.url && item.is_disabled() && item.adapter) item.close();
    }.bind(this));

    if (this.position == 'replace') {
      this.position = 'after';
      this.hide_target = true;
    }

    if (this.position == 'after') {
      new Insertion.After(this.target, content);
      this.adapter = this.target.next();
    }
    else if (this.position == 'before') {
      new Insertion.Before(this.target, content);
      this.adapter = this.target.previous();
    }
    else {
      return false;
    }

    this.adapter.down('a.inline-adapter-close').observe('click', function(event) {
      this.close_with_refresh();
      Event.stop(event);
    }.bind(this));

    new Effect.Highlight(this.adapter.down('td'));
  },

  close_with_refresh: function() {
    new Ajax.Request(this.refresh_url, {
      asynchronous: true,
      evalScripts: true,

      onSuccess: function(request) {
        Element.replace(this.target, request.responseText);
        var new_target = $(this.target.id);
        if (this.target.hasClassName('even')) new_target.addClassName('even');
        this.target = new_target;
        this.close();
      }.bind(this)
    });
  },

  enable: function() {
    this.set.links.each(function(item) {
      if (item.url != this.url) return;
      item.tag.removeClassName('disabled');
    }.bind(this));
  },

  disable: function() {
    this.set.links.each(function(item) {
      if (item.url != this.url) return;
      item.tag.addClassName('disabled');
    }.bind(this));
  }
});

/**
 * Concrete classes for table actions
 */
ActiveScaffold.Actions.Table = Class.create();
ActiveScaffold.Actions.Table.prototype = Object.extend(new ActiveScaffold.Actions.Abstract(), {
  instantiate_link: function(link) {
    var l = new ActiveScaffold.ActionLink.Table(link, this.target, this.loading_indicator);
    l.url = l.url.append_params({adapter: '_list_inline_adapter'});
    return l;
  }
});

ActiveScaffold.ActionLink.Table = Class.create();
ActiveScaffold.ActionLink.Table.prototype = Object.extend(new ActiveScaffold.ActionLink.Abstract(), {
  insert: function(content) {
    if (this.position == 'top') {
      new Insertion.Top(this.target, content);
      this.adapter = this.target.immediateDescendants().first();
    }
    else {
      throw 'Unknown position "' + this.position + '"'
    }

    this.adapter.down('a.inline-adapter-close').observe('click', function(event) {
      this.close();
      Event.stop(event);
    }.bind(this));

    new Effect.Highlight(this.adapter.down('td'));
  }
});<|endoftext|>


<commit_before>
/****************************************************************************
 LICENSE: CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 This is an original driver by Elijah Parker <mail@timelapseplus.com>
 It is free to use in other projects for non-commercial purposes.  For a
 commercial license and consulting, please contact mail@timelapseplus.com
*****************************************************************************/

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var usb = require('usb');

var api = new EventEmitter();

var DRIVERS = [];
DRIVERS.push(require('./drivers/fuji.js'));

function CameraAPI(driver) {
	this._driver = driver;

	this.exposure = {
		shutter: null,
		aperture: null,
		iso: null,
		shutterList: [],
		apertureList: [],
		isoList: []
	}
	this.status = { // read only
		busy: null,
		recording: null,
		remaining: null,
		battery: null,
		focusPos: null
	}
	this.config = { // can be set via CameraAPI.set()
		format: null,
		liveview: null,
		lvZoom: null,
		lvCenter: null,
		mode: null,
		af: null,
	}
	this.supports = { // read only
		shutter: null,
		aperture: null,
		iso: null,
		liveview: null,
		target: null,
		focus: null,
		video: null,
		trigger: null
	}
}

util.inherits(CameraAPI, EventEmitter);

usb.on('attach', function(device) {
	console.log("device attached", device);
	tryConnectDevice(device);
});

usb.on('detach', function(device) {
	//console.log("DETACHED:", device);
	var port = device.busNumber + ':' + device.deviceAddress;
	for(var i = 0; i < api.cameras.length; i++) {
		if(api.cameras[i]._port == port) api.emit('disconnected', camera.name, camera); // had been connected
	}
});

CameraAPI.prototype.set = function(parameter, value, callback) {
	return this._driver.set(this._dev, parameter, value, callback);
}

CameraAPI.prototype.init = function(callback) {
	return this._driver.init(this._dev, callback);
}

CameraAPI.prototype.capture = function(target, options, callback) {
	if(typeof options == 'function' && callback == undefined) {
		callback = options;
		options = {};
	}
	return this._driver.capture(this._dev, target, options, callback);
}

CameraAPI.prototype.liveviewMode = function(enable, callback) {
	if(!this.supports.liveview) return callback && callback("not supported");
	if(this.config.liveview === enable) return callback && callback();
	return this._driver.liveviewMode(this._dev, enable, callback);
}

CameraAPI.prototype.liveviewImage = function(enable, callback) {
	if(!this.supports.liveview) return callback && callback("not supported");
	if(!this.config.liveview) return callback && callback("not enabled");
	return this._driver.liveviewImage(this._dev, callback);
}

CameraAPI.prototype.moveFocus = function(steps, resolution, callback) {
	if(!this.supports.focus) return callback && callback("not supported");
	return this._driver.moveFocus(this._dev, enable, callback);
}

function connectCamera(driver, device) {
	var camera = new CameraAPI(driver);
	device.open();
	var iface = device.interfaces[0];
	iface.claim();
	var cam = {
		ep: {
			in: null,
			out: null,
			evt: null
		},
		transactionId: 0
	};
	for(var i = 0; i < iface.endpoints.length; i++) {
		var ep = iface.endpoints[i];
		if(ep.transferType == 2 && ep.direction == 'in') cam.ep.in = ep;
		if(ep.transferType == 2 && ep.direction == 'out') cam.ep.out = ep;
		if(ep.transferType == 3 && ep.direction == 'in') cam.ep.evt = ep;
	}
	camera._dev = cam;
	if(cam.ep.evt) {
		cam.ep.evt.startPoll();
		cam.ep.evt.on('data', function(data) {
			camera._driver._event(camera._dev, data);
		});
	}
	camera._port = device.busNumber + ':' + device.deviceAddress;
	return camera;
}

function fourHex(n) {
	n = n || 0;
	n = parseInt(n);
	n = n.toString(16);
	while(n.length < 4) n = '0' + n;
	return n;
}

function matchDriver(device) {
	if(device && device.deviceDescriptor) {
		var id  = fourHex(device.deviceDescriptor.idVendor) + ':' + fourHex(device.deviceDescriptor.idProduct);
		for(var i = 0; i < DRIVERS.length; i++) {
			if(DRIVERS[i].supportedCameras[id]) {
				return {
					driver: DRIVERS[i],
					name: DRIVERS[i].supportedCameras[id].name
				}
			}
		}
	}
	return null;
}

api.cameras = [];

function tryConnectDevice(device) {
	var port = device.busNumber + ':' + device.deviceAddress;
	for(var i = 0; i < api.cameras.length; i++) {
		if(api.cameras[i]._port == port) return; // already connected
	}
	var found = matchDriver(device);
	if(found) {
		console.log("camera connected:", found.name);
		var camera = connectCamera(found.driver, device);
		camera.init(function(err) {
			api.cameras.push({
				name: found.name,
				camera: camera
			});
			api.emit('connected', found.name);
		});
	}
}

var devices = usb.getDeviceList();
for(var i = 0; i < devices.length; i++) {
	tryConnectDevice(devices[i]);
}

console.log("ready");

module.exports = api;
<commit_msg>usb testing<commit_after>
/****************************************************************************
 LICENSE: CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 This is an original driver by Elijah Parker <mail@timelapseplus.com>
 It is free to use in other projects for non-commercial purposes.  For a
 commercial license and consulting, please contact mail@timelapseplus.com
*****************************************************************************/

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var usb = require('usb');

var api = new EventEmitter();

var DRIVERS = [];
DRIVERS.push(require('./drivers/fuji.js'));

function CameraAPI(driver) {
	this._driver = driver;

	this.exposure = {
		shutter: null,
		aperture: null,
		iso: null,
		shutterList: [],
		apertureList: [],
		isoList: []
	}
	this.status = { // read only
		busy: null,
		recording: null,
		remaining: null,
		battery: null,
		focusPos: null
	}
	this.config = { // can be set via CameraAPI.set()
		format: null,
		liveview: null,
		lvZoom: null,
		lvCenter: null,
		mode: null,
		af: null,
	}
	this.supports = { // read only
		shutter: null,
		aperture: null,
		iso: null,
		liveview: null,
		target: null,
		focus: null,
		video: null,
		trigger: null
	}
}

util.inherits(CameraAPI, EventEmitter);

usb.on('attach', function(device) {
	//console.log("device attached", device);
	tryConnectDevice(device);
});

usb.on('detach', function(device) {
	//console.log("DETACHED:", device);
	var port = device.busNumber + ':' + device.deviceAddress;
	for(var i = 0; i < api.cameras.length; i++) {
		if(api.cameras[i]._port == port) api.emit('disconnected', camera.name, camera); // had been connected
	}
});

CameraAPI.prototype.set = function(parameter, value, callback) {
	return this._driver.set(this._dev, parameter, value, callback);
}

CameraAPI.prototype.init = function(callback) {
	console.log("init:", this._dev);
	return this._driver.init(this._dev, callback);
}

CameraAPI.prototype.capture = function(target, options, callback) {
	if(typeof options == 'function' && callback == undefined) {
		callback = options;
		options = {};
	}
	return this._driver.capture(this._dev, target, options, callback);
}

CameraAPI.prototype.liveviewMode = function(enable, callback) {
	if(!this.supports.liveview) return callback && callback("not supported");
	if(this.config.liveview === enable) return callback && callback();
	return this._driver.liveviewMode(this._dev, enable, callback);
}

CameraAPI.prototype.liveviewImage = function(enable, callback) {
	if(!this.supports.liveview) return callback && callback("not supported");
	if(!this.config.liveview) return callback && callback("not enabled");
	return this._driver.liveviewImage(this._dev, callback);
}

CameraAPI.prototype.moveFocus = function(steps, resolution, callback) {
	if(!this.supports.focus) return callback && callback("not supported");
	return this._driver.moveFocus(this._dev, enable, callback);
}

function connectCamera(driver, device) {
	var camera = new CameraAPI(driver);
	device.open();
	var iface = device.interfaces[0];
	iface.claim();
	var cam = {
		ep: {
			in: null,
			out: null,
			evt: null
		},
		transactionId: 0
	};
	for(var i = 0; i < iface.endpoints.length; i++) {
		var ep = iface.endpoints[i];
		if(ep.transferType == 2 && ep.direction == 'in') cam.ep.in = ep;
		if(ep.transferType == 2 && ep.direction == 'out') cam.ep.out = ep;
		if(ep.transferType == 3 && ep.direction == 'in') cam.ep.evt = ep;
	}
	camera._dev = cam;
	if(cam.ep.evt) {
		cam.ep.evt.startPoll();
		cam.ep.evt.on('data', function(data) {
			camera._driver._event(camera._dev, data);
		});
	}
	camera._port = device.busNumber + ':' + device.deviceAddress;
	return camera;
}

function fourHex(n) {
	n = n || 0;
	n = parseInt(n);
	n = n.toString(16);
	while(n.length < 4) n = '0' + n;
	return n;
}

function matchDriver(device) {
	if(device && device.deviceDescriptor) {
		var id  = fourHex(device.deviceDescriptor.idVendor) + ':' + fourHex(device.deviceDescriptor.idProduct);
		for(var i = 0; i < DRIVERS.length; i++) {
			if(DRIVERS[i].supportedCameras[id]) {
				return {
					driver: DRIVERS[i],
					name: DRIVERS[i].supportedCameras[id].name
				}
			}
		}
	}
	return null;
}

api.cameras = [];

function tryConnectDevice(device) {
	var port = device.busNumber + ':' + device.deviceAddress;
	for(var i = 0; i < api.cameras.length; i++) {
		if(api.cameras[i]._port == port) return; // already connected
	}
	var found = matchDriver(device);
	if(found) {
		console.log("camera connected:", found.name);
		var camera = connectCamera(found.driver, device);
		camera.init(function(err) {
			api.cameras.push({
				name: found.name,
				camera: camera
			});
			api.emit('connected', found.name);
		});
	}
}

var devices = usb.getDeviceList();
for(var i = 0; i < devices.length; i++) {
	tryConnectDevice(devices[i]);
}

console.log("ready");

module.exports = api;
<|endoftext|>


<commit_before>var cdc = require('../lib/statsd-client'),
    CDC = new cdc({host: '127.0.0.1', tcp: true, debug: true});

var siteId = 'siteId';
var pathHash = 'pathHash';
var campaignId = 'campaignId';

CDC.put('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 50', 5);
CDC.increment('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 50', 1);
CDC.decrement('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 50', 2);
<commit_msg>more tests<commit_after>var cdc = require('../lib/statsd-client'),
    CDC = new cdc({host: '127.0.0.1', tcp: true, debug: true});

var siteId = 'siteId';
var pathHash = 'pathHash';
var campaignId = 'campaignId';

CDC.put('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 50', 5);
CDC.increment('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 50', 1);
CDC.decrement('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 50', 2);

CDC.increment('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 40', 1);

CDC.decrement('a25489e9-848c-4356-97ff-161b0852c509', siteId, pathHash + ':' + campaignId, 'Percent 60', 2);
<|endoftext|>


<commit_before>import GenericResolver from 'api/resolver/generic/genericResolver'
import {ObjectId} from 'mongodb'
import Hook from 'util/hook'
import Util from 'api/util'
import Cache from 'util/cache'
import {
    CAPABILITY_MANAGE_KEYVALUES
} from 'util/capabilities'

const UtilCms = {
    getCmsPage: async ({db, context, slug, _version, headers}) => {
        const userIsLoggedIn = Util.isUserLoggedIn(context)
        let host
        if (headers) {
            if (headers.forwarded) {
                // if request comes from proxy server
                const match = headers.forwarded.match(/(?<=(^|; )host=).*(?=(;|$))/)
                if (match) {
                    host = match[0].split(':')[0]
                }
            }

            if (!host) {
                host = headers.host.split(':')[0]
            }
        }
        host='www.onyou.ch'

        const cacheKey = 'cmsPage-' + _version + '-' + slug + (host ? '-' + host : '')

        let cmsPages
        if (!userIsLoggedIn) {
            // get page from cache if user is not loggedin
            cmsPages = Cache.get(cacheKey)
        }
        if (!cmsPages) {

            let match, hostRule

            if (host) {
                hostRule = {$regex: `(^|;)${host.replace(/\./g, '\\.')}=${slug}($|;)`, $options: 'i'}
            }

            // slugMatch.$regex = 'test|'+slug
            if (!userIsLoggedIn) {
                // if no user only match public entries
                if (hostRule) {
                    match = {$and: [{$or: [{hostRule},{slug}]}, {public: true}]}
                } else {
                    match = {$and: [{slug}, {public: true}]}
                }
            } else {
                if (hostRule) {
                    match = {$or: [{hostRule},{slug}]}
                } else {
                    match = {slug}
                }
            }

            cmsPages = await GenericResolver.entities(db, context, 'CmsPage', ['slug', 'name', 'template', 'script', 'dataResolver', 'resources', 'ssr', 'public', 'urlSensitiv'], {
                match,
                _version
            })

            // minify template if no user is logged in
            if (!userIsLoggedIn && cmsPages.results && cmsPages.results.length) {

                // TODO: maybe it is better to store the template already minified in the collection instead of minify it here
                //console.log(cmsPages.results[0].template)
                try {
                    cmsPages.results[0].template = JSON.stringify(JSON.parse(cmsPages.results[0].template), null, 0)
                } catch (e) {
                }
            }
            Cache.set(cacheKey, cmsPages, 60000) // cache expires in 1 min
        }
        return cmsPages
    },
    resolveData: async (db, context, dataResolver, scope, nosession) => {
        const resolvedData = {_meta: {}}, subscriptions = []

        if (dataResolver) {
            let debugInfo = null
            try {
                let segments = JSON.parse(dataResolver)
                if (segments.constructor === Object) segments = [segments]

                for (let i = 0; i < segments.length; i++) {

                    debugInfo = ''
                    const tpl = new Function('const {' + Object.keys(scope).join(',') + '} = this.scope;const {data} = this; return `' + JSON.stringify(segments[i]) + '`;')
                    const replacedSegmentStr = tpl.call({scope, data: resolvedData})
                    const segment = JSON.parse(replacedSegmentStr)
                    if (segment.data) {
                        Object.keys(segment.data).forEach(k => {
                            resolvedData[k] = segment.data[k]
                        })
                    } else if (segment.t) {
                        const {t, f, l, o, p, d, s} = segment
                        /*
                         f = filter for the query
                         t = type
                         s = sort
                         d = the data / fields you want to access
                         l = limit of results
                         o = offset
                         p = page (if no offset is defined, offset is limit * (page -1) )
                         */

                        let type, match
                        if (t.indexOf('$') === 0) {
                            type = t.substring(1)
                            subscriptions.push(type)
                        } else {
                            type = t
                        }

                        // restriction = if it is set to 'user' only entries that belongs to the user are returned
                        if (segment.restriction && segment.restriction === 'user') {
                            match = {createdBy: ObjectId(context.id)}
                        } else {
                            match = {}
                        }

                        debugInfo += ' type=' + type
                        const result = await GenericResolver.entities(db, context, type, d, {
                            filter: f,
                            limit: l,
                            page: p,
                            sort: s,
                            offset: o,
                            match,
                            projectResult: true
                        })
                        debugInfo += ' result=true'

                        resolvedData[segment.key || type] = result
                    } else if (segment.eval) {
                        debugInfo += ' in eval'
                        try {
                            const tpl = new Function('const {' + Object.keys(scope).join(',') + '} = this.scope; const {data} = this;' + segment.eval)
                            tpl.call({data: resolvedData, scope, context})
                        } catch (e) {
                            if (!segment.ignoreError)
                                throw e
                        }
                    } else if (segment.subscription) {
                        if (segment.subscription.constructor === String) {
                            subscriptions.push(segment.subscription)
                        } else {
                            subscriptions.push(JSON.stringify(segment.subscription))
                        }
                    } else if (segment.system) {
                        const system = Util.systemProperties()
                        resolvedData._meta.system = segment.key || 'system'
                        resolvedData[resolvedData._meta.system] = system
                    } else if (segment.keyValueGlobals) {
                        const match = {key: {$in: segment.keyValueGlobals}}

                        // if user don't have capability to manage keys he can only see the public ones
                        if (!await Util.userHasCapability(db, context, CAPABILITY_MANAGE_KEYVALUES)) {
                            match.ispublic = true
                        }

                        const result = await GenericResolver.entities(db, context, 'KeyValueGlobal', ['key', 'value'], {
                            match
                        })
                        const map = {}
                        if (result.results) {
                            result.results.map(entry => {
                                console.log(entry)
                                try {
                                    map[entry.key] = JSON.parse(entry.value)
                                } catch (e) {
                                    map[entry.key] = entry.value
                                }
                            })
                        }
                        resolvedData._meta.keyValueGlobalKey = segment.key || 'keyValueGlobals'
                        resolvedData[resolvedData._meta.keyValueGlobalKey] = map
                    } else if (segment.keyValues) {

                        const map = {}

                        if (Util.isUserLoggedIn(context)) {
                            const match = {createdBy: ObjectId(context.id), key: {$in: segment.keyValues}}
                            const result = await GenericResolver.entities(db, context, 'KeyValue', ['key', 'value'], {
                                match
                            })

                            if (result.results) {
                                result.results.map(entry => {
                                    try {
                                        map[entry.key] = JSON.parse(entry.value)
                                    } catch (e) {
                                        map[entry.key] = entry.value
                                    }
                                })
                            }
                        } else if (nosession) {
                            let nosessionJson
                            try {
                                nosessionJson = JSON.parse(nosession)
                            } catch (e) {
                                nosessionJson = {}
                            }
                            segment.keyValues.forEach(key => {
                                if (nosessionJson[key]) {
                                    try {
                                        map[key] = JSON.parse(nosessionJson[key])
                                    } catch (e) {
                                        map[key] = nosessionJson[key]
                                    }
                                }
                            })
                        }

                        resolvedData._meta.keyValueKey = segment.key || 'keyValues'
                        resolvedData[resolvedData._meta.keyValueKey] = map

                    } else {
                        console.log('call cmsCustomResolver', segment)
                        Hook.call('cmsCustomResolver', {resolvedData, resolver: segment})
                    }

                }
            } catch (e) {
                resolvedData.error = e.message + ' -> scope=' + JSON.stringify(scope) + debugInfo
            }
        }
        return {resolvedData, subscriptions}
    }
}

export default UtilCms<commit_msg>Small improvments<commit_after>import GenericResolver from 'api/resolver/generic/genericResolver'
import {ObjectId} from 'mongodb'
import Hook from 'util/hook'
import Util from 'api/util'
import Cache from 'util/cache'
import {
    CAPABILITY_MANAGE_KEYVALUES
} from 'util/capabilities'

const UtilCms = {
    getCmsPage: async ({db, context, slug, _version, headers}) => {
        const userIsLoggedIn = Util.isUserLoggedIn(context)
        let host
        if (headers) {
            if (headers.forwarded) {
                // if request comes from proxy server
                const match = headers.forwarded.match(/(?<=(^|; )host=).*(?=(;|$))/)
                if (match) {
                    host = match[0].split(':')[0]
                }
            }

            if (!host) {
                host = headers.host.split(':')[0]
            }
        }

        const cacheKey = 'cmsPage-' + _version + '-' + slug + (host ? '-' + host : '')

        let cmsPages
        if (!userIsLoggedIn) {
            // get page from cache if user is not loggedin
            cmsPages = Cache.get(cacheKey)
        }
        if (!cmsPages) {

            let match, hostRule

            if (host) {
                hostRule = {$regex: `(^|;)${host.replace(/\./g, '\\.')}=${slug}($|;)`, $options: 'i'}
            }

            // slugMatch.$regex = 'test|'+slug
            if (!userIsLoggedIn) {
                // if no user only match public entries
                if (hostRule) {
                    match = {$and: [{$or: [{hostRule},{slug}]}, {public: true}]}
                } else {
                    match = {$and: [{slug}, {public: true}]}
                }
            } else {
                if (hostRule) {
                    match = {$or: [{hostRule},{slug}]}
                } else {
                    match = {slug}
                }
            }

            cmsPages = await GenericResolver.entities(db, context, 'CmsPage', ['slug', 'name', 'template', 'script', 'dataResolver', 'resources', 'ssr', 'public', 'urlSensitiv'], {
                match,
                _version
            })

            // minify template if no user is logged in
            if (!userIsLoggedIn && cmsPages.results && cmsPages.results.length) {

                // TODO: maybe it is better to store the template already minified in the collection instead of minify it here
                //console.log(cmsPages.results[0].template)
                try {
                    cmsPages.results[0].template = JSON.stringify(JSON.parse(cmsPages.results[0].template), null, 0)
                } catch (e) {
                }
            }
            Cache.set(cacheKey, cmsPages, 60000) // cache expires in 1 min
        }
        return cmsPages
    },
    resolveData: async (db, context, dataResolver, scope, nosession) => {
        const resolvedData = {_meta: {}}, subscriptions = []

        if (dataResolver) {
            let debugInfo = null
            try {
                let segments = JSON.parse(dataResolver)
                if (segments.constructor === Object) segments = [segments]

                for (let i = 0; i < segments.length; i++) {

                    debugInfo = ''
                    const tpl = new Function('const {' + Object.keys(scope).join(',') + '} = this.scope;const {data} = this; return `' + JSON.stringify(segments[i]) + '`;')
                    const replacedSegmentStr = tpl.call({scope, data: resolvedData})
                    const segment = JSON.parse(replacedSegmentStr)
                    if (segment.data) {
                        Object.keys(segment.data).forEach(k => {
                            resolvedData[k] = segment.data[k]
                        })
                    } else if (segment.t) {
                        const {t, f, l, o, p, d, s} = segment
                        /*
                         f = filter for the query
                         t = type
                         s = sort
                         d = the data / fields you want to access
                         l = limit of results
                         o = offset
                         p = page (if no offset is defined, offset is limit * (page -1) )
                         */

                        let type, match
                        if (t.indexOf('$') === 0) {
                            type = t.substring(1)
                            subscriptions.push(type)
                        } else {
                            type = t
                        }

                        // restriction = if it is set to 'user' only entries that belongs to the user are returned
                        if (segment.restriction && segment.restriction === 'user') {
                            match = {createdBy: ObjectId(context.id)}
                        } else {
                            match = {}
                        }

                        debugInfo += ' type=' + type
                        const result = await GenericResolver.entities(db, context, type, d, {
                            filter: f,
                            limit: l,
                            page: p,
                            sort: s,
                            offset: o,
                            match,
                            projectResult: true
                        })
                        debugInfo += ' result=true'

                        resolvedData[segment.key || type] = result
                    } else if (segment.eval) {
                        debugInfo += ' in eval'
                        try {
                            const tpl = new Function('const {' + Object.keys(scope).join(',') + '} = this.scope; const {data} = this;' + segment.eval)
                            tpl.call({data: resolvedData, scope, context})
                        } catch (e) {
                            if (!segment.ignoreError)
                                throw e
                        }
                    } else if (segment.subscription) {
                        if (segment.subscription.constructor === String) {
                            subscriptions.push(segment.subscription)
                        } else {
                            subscriptions.push(JSON.stringify(segment.subscription))
                        }
                    } else if (segment.system) {
                        const system = Util.systemProperties()
                        resolvedData._meta.system = segment.key || 'system'
                        resolvedData[resolvedData._meta.system] = system
                    } else if (segment.keyValueGlobals) {
                        const match = {key: {$in: segment.keyValueGlobals}}

                        // if user don't have capability to manage keys he can only see the public ones
                        if (!await Util.userHasCapability(db, context, CAPABILITY_MANAGE_KEYVALUES)) {
                            match.ispublic = true
                        }

                        const result = await GenericResolver.entities(db, context, 'KeyValueGlobal', ['key', 'value'], {
                            match
                        })
                        const map = {}
                        if (result.results) {
                            result.results.map(entry => {
                                console.log(entry)
                                try {
                                    map[entry.key] = JSON.parse(entry.value)
                                } catch (e) {
                                    map[entry.key] = entry.value
                                }
                            })
                        }
                        resolvedData._meta.keyValueGlobalKey = segment.key || 'keyValueGlobals'
                        resolvedData[resolvedData._meta.keyValueGlobalKey] = map
                    } else if (segment.keyValues) {

                        const map = {}

                        if (Util.isUserLoggedIn(context)) {
                            const match = {createdBy: ObjectId(context.id), key: {$in: segment.keyValues}}
                            const result = await GenericResolver.entities(db, context, 'KeyValue', ['key', 'value'], {
                                match
                            })

                            if (result.results) {
                                result.results.map(entry => {
                                    try {
                                        map[entry.key] = JSON.parse(entry.value)
                                    } catch (e) {
                                        map[entry.key] = entry.value
                                    }
                                })
                            }
                        } else if (nosession) {
                            let nosessionJson
                            try {
                                nosessionJson = JSON.parse(nosession)
                            } catch (e) {
                                nosessionJson = {}
                            }
                            segment.keyValues.forEach(key => {
                                if (nosessionJson[key]) {
                                    try {
                                        map[key] = JSON.parse(nosessionJson[key])
                                    } catch (e) {
                                        map[key] = nosessionJson[key]
                                    }
                                }
                            })
                        }

                        resolvedData._meta.keyValueKey = segment.key || 'keyValues'
                        resolvedData[resolvedData._meta.keyValueKey] = map

                    } else {
                        console.log('call cmsCustomResolver', segment)
                        Hook.call('cmsCustomResolver', {resolvedData, resolver: segment})
                    }

                }
            } catch (e) {
                resolvedData.error = e.message + ' -> scope=' + JSON.stringify(scope) + debugInfo
            }
        }
        return {resolvedData, subscriptions}
    }
}

export default UtilCms<|endoftext|>


<commit_before>/**
 *
 *  Defines `hNumberPicker` directive which can only be used as element.
 *
 *  It allows end-user to choose number, instead of typing
 *
 *  usage:
 *
 *       <h-number value="input.num" min="1" max="10" step="1" change="onChange()"></h-number>
 *
 *  @author  Howard.Zuo
 *  @date    Feb 6th, 2016
 *
 */
(function(global) {
    'use strict';

    var definition = function(angular) {

        var defaults = {
            min: 0,
            max: 100,
            step: 1,
            timeout: 600
        };

        var assign = function(dest, src) {
            for (var key in src) {
                if (!dest[key]) {
                    dest[key] = src[key];
                }
            }
            return dest;
        };

        var isNumber = function(value) {
            var val = Number(value);
            return !isNaN(val) && val == value;
        };

        var toNumber = function(value) {
            return Number(value);
        };

        var checkNumber = function(value) {
            if (!isNumber(value)) {
                throw new Error('value [' + value + '] is not a valid number');
            }
        };

        var getTarget = function(e) {
            if (e.touches && e.touches.length > 0) {
                return angular.element(e.touches[0].target);
            }
            return angular.element(e.target);
        };

        var getType = function(e) {
            return getTarget(e).attr('type');
        };

        var transform = function(opts) {
            for (var key in opts) {
                var value = opts[key];
                opts[key] = toNumber(value);
            }
        };

        var directive = function($timeout, $interval) {

            return {
                restrict: 'E',
                scope: {
                    'value': '=',
                    'singular': '@',
                    'plural': '@',
                    'min': '@',
                    'max': '@',
                    'step': '@',
                    'change': '&'
                },
                link: function($scope, element) {

                    var opts = assign({
                        min: $scope.min,
                        max: $scope.max,
                        step: $scope.step
                    }, defaults);

                    checkNumber(opts.min);
                    checkNumber(opts.max);
                    checkNumber(opts.step);

                    transform(opts);

                    if (opts.min > $scope.value) {
                        $scope.value = opts.min;
                    }

                    $scope.$watch('value', function(newValue, oldValue) {
                        $scope.canDown = newValue > opts.min;
                        $scope.canUp = newValue < opts.max;

                        if (newValue !== oldValue) {
                            $scope.change();
                        }
                    });

                    var changeNumber = function($event) {
                        var type = getType($event);
                        if ('up' === type) {
                            if ($scope.value >= opts.max) {
                                return;
                            }
                            $scope.value += opts.step;
                            if ($scope.value > opts.max) {
                                $scope.value = opts.max;
                            }
                        } else if ('down' === type) {
                            if ($scope.value <= opts.min) {
                                return;
                            }
                            $scope.value -= opts.step;
                            if ($scope.value < opts.min) {
                                $scope.value = opts.min;
                            }
                        }
                    };

                    var isPressing;
                    var timeoutPro;
                    var intervalPro;
                    var start;
                    var end;
                    var addon = element.find('span');

                    addon.on('click', function(e) {
                        changeNumber(e);
                        $scope.$apply();
                        e.stopPropagation();
                    });

                    addon.on('touchstart', function(e) {
                        if (isPressing) {
                            return;
                        }
                        isPressing = true;
                        getTarget(e).addClass('active');
                        start = new Date().getTime();
                        timeoutPro = $timeout(function() {
                            intervalPro = $interval(function() {
                                changeNumber(e);
                            }, 200);
                        }, opts.timeout);
                        e.preventDefault();
                    });

                    addon.on('touchend', function(e) {
                        end = new Date().getTime();
                        if (intervalPro) {
                            $interval.cancel(intervalPro);
                            intervalPro = undefined;
                        }
                        if (timeoutPro) {
                            $timeout.cancel(timeoutPro);
                            timeoutPro = undefined;
                        }
                        if ((end - start) < opts.timeout) {
                            changeNumber(e);
                            $scope.$apply();
                        }
                        getTarget(e).removeClass('active');
                        e.stopPropagation();
                        isPressing = false;
                    });

                    $scope.$on('$destroy', function() {
                        addon.off('touchstart touchend click');
                    });

                },
                template: '
  -  {{ value }} {{value === 1 ? singular : plural}}  +
'
            };
        };

        var name = 'angularNumberPicker';
        angular.module(name, [])
            .directive('hNumber', ['$timeout', '$interval', directive]);
        return name;
    };

    if (typeof exports === 'object') {
        module.exports = definition(require('angular'));
    } else if (typeof define === 'function' && define.amd) {
        define(['angular'], definition);
    } else {
        definition(global.angular);
    }

}(window));
<commit_msg>formatter<commit_after>/**
 *
 *  Defines `hNumberPicker` directive which can only be used as element.
 *
 *  It allows end-user to choose number, instead of typing
 *
 *  usage:
 *
 *       <h-number value="input.num" min="1" max="10" step="1" change="onChange()"></h-number>
 *
 *  @author  Howard.Zuo
 *  @date    Feb 6th, 2016
 *
 */
(function(global) {
    'use strict';

    var definition = function(angular) {

        var defaults = {
            min: 0,
            max: 100,
            step: 1,
            timeout: 600
        };

        var assign = function(dest, src) {
            for (var key in src) {
                if (!dest[key]) {
                    dest[key] = src[key];
                }
            }
            return dest;
        };

        var isNumber = function(value) {
            var val = Number(value);
            return !isNaN(val) && val === value;
        };

        var toNumber = function(value) {
            return Number(value);
        };

        var checkNumber = function(value) {
            if (!isNumber(value)) {
                throw new Error('value [' + value + '] is not a valid number');
            }
        };

        var getTarget = function(e) {
            if (e.touches && e.touches.length > 0) {
                return angular.element(e.touches[0].target);
            }
            return angular.element(e.target);
        };

        var getType = function(e) {
            return getTarget(e).attr('type');
        };

        var transform = function(opts) {
            for (var key in opts) {
                var value = opts[key];
                opts[key] = toNumber(value);
            }
        };

        var directive = function($timeout, $interval) {

            return {
                restrict: 'E',
                scope: {
                    value: '=',
                    singular: '@',
                    plural: '@',
                    min: '@',
                    max: '@',
                    step: '@',
                    change: '&'
                },
                link: function($scope, element) {

                    var opts = assign({
                        min: $scope.min,
                        max: $scope.max,
                        step: $scope.step
                    }, defaults);

                    checkNumber(opts.min);
                    checkNumber(opts.max);
                    checkNumber(opts.step);

                    transform(opts);

                    if (opts.min > $scope.value) {
                        $scope.value = opts.min;
                    }

                    $scope.$watch('value', function(newValue, oldValue) {
                        $scope.canDown = newValue > opts.min;
                        $scope.canUp = newValue < opts.max;

                        if (newValue !== oldValue) {
                            $scope.change();
                        }
                    });

                    var changeNumber = function($event) {
                        var type = getType($event);
                        if (type === 'up') {
                            if ($scope.value >= opts.max) {
                                return;
                            }
                            $scope.value += opts.step;
                            if ($scope.value > opts.max) {
                                $scope.value = opts.max;
                            }
                        } else if (type === 'down') {
                            if ($scope.value <= opts.min) {
                                return;
                            }
                            $scope.value -= opts.step;
                            if ($scope.value < opts.min) {
                                $scope.value = opts.min;
                            }
                        }
                    };

                    var isPressing;
                    var timeoutPro;
                    var intervalPro;
                    var start;
                    var end;
                    var addon = element.find('span');

                    addon.on('click', function(e) {
                        changeNumber(e);
                        $scope.$apply();
                        e.stopPropagation();
                    });

                    addon.on('touchstart', function(e) {
                        if (isPressing) {
                            return;
                        }
                        isPressing = true;
                        getTarget(e).addClass('active');
                        start = new Date().getTime();
                        timeoutPro = $timeout(function() {
                            intervalPro = $interval(function() {
                                changeNumber(e);
                            }, 200);
                        }, opts.timeout);
                        e.preventDefault();
                    });

                    addon.on('touchend', function(e) {
                        end = new Date().getTime();
                        if (intervalPro) {
                            $interval.cancel(intervalPro);
                            intervalPro = undefined;
                        }
                        if (timeoutPro) {
                            $timeout.cancel(timeoutPro);
                            timeoutPro = undefined;
                        }
                        if ((end - start) < opts.timeout) {
                            changeNumber(e);
                            $scope.$apply();
                        }
                        getTarget(e).removeClass('active');
                        e.stopPropagation();
                        isPressing = false;
                    });

                    $scope.$on('$destroy', function() {
                        addon.off('touchstart touchend click');
                    });

                },
                template: '
  -  {{ value }} {{value === 1 ? singular : plural}}  +
'
            };
        };

        var name = 'angularNumberPicker';
        angular.module(name, [])
            .directive('hNumber', ['$timeout', '$interval', directive]);
        return name;
    };

    if (typeof exports === 'object') {
        module.exports = definition(require('angular'));
    } else if (typeof define === 'function' && define.amd) {
        define(['angular'], definition);
    } else {
        definition(global.angular);
    }

}(window));
<|endoftext|>


<commit_before>import { isFunction } from './utils/index'
import CanvasToBase64 from './utils/canvas2base64'

export default function methods () {
  const self = this

  const { id, deviceRadio } = self
  const boundWidth = self.width // 裁剪框默认宽度，即整个画布宽度
  const boundHeight = self.height // 裁剪框默认高度，即整个画布高度
  let { x = 0, y = 0, width = boundWidth, height = boundHeight } = self.cut

  self.updateCanvas = () => {
    if (self.croperTarget) {
      //  画布绘制图片
      self.ctx.drawImage(self.croperTarget, self.imgLeft, self.imgTop, self.scaleWidth, self.scaleHeight)
    }
    isFunction(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self)

    self.setBoundStyle() //	设置边界样式
    self.ctx.draw()
    return self
  }

  self.pushOrign = (src) => {
    self.src = src

    isFunction(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self)

    wx.getImageInfo({
      src,
      success (res) {
        let innerAspectRadio = res.width / res.height

        self.croperTarget = res.path

        if (innerAspectRadio < width / height) {
          self.rectX = x
          self.baseWidth = width
          self.baseHeight = width / innerAspectRadio
          self.rectY = y - Math.abs((height - self.baseHeight) / 2)
        } else {
          self.rectY = y
          self.baseWidth = height * innerAspectRadio
          self.baseHeight = height
          self.rectX = x - Math.abs((width - self.baseWidth) / 2)
        }

        self.imgLeft = self.rectX
        self.imgTop = self.rectY
        self.scaleWidth = self.baseWidth
        self.scaleHeight = self.baseHeight

        self.updateCanvas()

        isFunction(self.onImageLoad) && self.onImageLoad(self.ctx, self)
      }
    })

    self.update()
    return self
  }

  self.getCropperBase64 = (done = () => {}) => {
    CanvasToBase64.convertToBMP({
      canvasId: id,
      x,
      y,
      width,
      height
    }, done)
  }

  self.getCropperImage = (...args) => {
    const ARG_TYPE = toString.call(args[0])
    const fn = args[args.length - 1]

    switch (ARG_TYPE) {
      case '[object Object]':
        let { quality = 10 } = args[0]

        if (typeof (quality) !== 'number') {
          console.error(`quality：${quality} is invalid`)
        } else if (quality < 0 || quality > 10) {
          console.error(`quality should be ranged in 0 ~ 10`)
        }
        wx.canvasToTempFilePath({
          canvasId: id,
          x,
          y,
          width,
          height,
          destWidth: width * quality / (deviceRadio * 10),
          destHeight: height * quality / (deviceRadio * 10),
          success (res) {
            isFunction(fn) && fn.call(self, res.tempFilePath)
          },
          fail (res) {
            isFunction(fn) && fn.call(self, null)
          }
        }); break
      case '[object Function]':
        wx.canvasToTempFilePath({
          canvasId: id,
          x,
          y,
          width,
          height,
          destWidth: width / deviceRadio,
          destHeight: height / deviceRadio,
          success (res) {
            isFunction(fn) && fn.call(self, res.tempFilePath)
          },
          fail (res) {
            isFunction(fn) && fn.call(self, null)
          }
        }); break
    }

    return self
  }
}
<commit_msg>Fixed ci<commit_after>import { isFunction } from './utils/index'
import CanvasToBase64 from './utils/canvas2base64'

export default function methods () {
  const self = this

  const { id, deviceRadio } = self
  const boundWidth = self.width // 裁剪框默认宽度，即整个画布宽度
  const boundHeight = self.height // 裁剪框默认高度，即整个画布高度
  let { x = 0, y = 0, width = boundWidth, height = boundHeight } = self.cut

  self.updateCanvas = () => {
    if (self.croperTarget) {
      //  画布绘制图片
      self.ctx.drawImage(self.croperTarget, self.imgLeft, self.imgTop, self.scaleWidth, self.scaleHeight)
    }
    isFunction(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self)

    self.setBoundStyle() //	设置边界样式
    self.ctx.draw()
    return self
  }

  self.pushOrign = (src) => {
    self.src = src

    isFunction(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self)

    wx.getImageInfo({
      src,
      success (res) {
        let innerAspectRadio = res.width / res.height

        self.croperTarget = res.path

        if (innerAspectRadio < width / height) {
          self.rectX = x
          self.baseWidth = width
          self.baseHeight = width / innerAspectRadio
          self.rectY = y - Math.abs((height - self.baseHeight) / 2)
        } else {
          self.rectY = y
          self.baseWidth = height * innerAspectRadio
          self.baseHeight = height
          self.rectX = x - Math.abs((width - self.baseWidth) / 2)
        }

        self.imgLeft = self.rectX
        self.imgTop = self.rectY
        self.scaleWidth = self.baseWidth
        self.scaleHeight = self.baseHeight

        self.updateCanvas()

        isFunction(self.onImageLoad) && self.onImageLoad(self.ctx, self)
      }
    })

    self.update()
    return self
  }

  self.getCropperBase64 = (done = () => {}) => {
    CanvasToBase64.convertToBMP({
      canvasId: id,
      x,
      y,
      width,
      height
    }, done)
  }

  self.getCropperImage = (...args) => {
    const ARG_TYPE = toString.call(args[0])
    const fn = args[args.length - 1]

    switch (ARG_TYPE) {
      case '[object Object]':
        let { quality = 10 } = args[0]

        if (typeof (quality) !== 'number') {
          console.error(`quality：${quality} is invalid`)
        } else if (quality < 0 || quality > 10) {
          console.error(`quality should be ranged in 0 ~ 10`)
        }
        wx.canvasToTempFilePath({
          canvasId: id,
          x,
          y,
          width,
          height,
          destWidth: width * quality / (deviceRadio * 10),
          destHeight: height * quality / (deviceRadio * 10),
          success (res) {
            isFunction(fn) && fn.call(self, res.tempFilePath)
          },
          fail (res) {
            isFunction(fn) && fn.call(self, null)
          }
        }); break
      case '[object Function]':
        wx.canvasToTempFilePath({
          canvasId: id,
          x,
          y,
          width,
          height,
          destWidth: width / deviceRadio,
          destHeight: height / deviceRadio,
          success (res) {
            isFunction(fn) && fn.call(self, res.tempFilePath)
          },
          fail (res) {
            isFunction(fn) && fn.call(self, null)
          }
        }); break
    }

    return self
  }
}
<|endoftext|>


<commit_before>'use strict'

var EVENTS_ENDPOINT = '/events',
    shoe = require('shoe'),
    $ = require('zeptojs'),
    _ = require('lodash'),
    hmac = require('crypto-js/hmac-sha1'),
    eventEmitter = require('event-emitter'),
    exercises = require('gitstream-exercises/viewers'),
    ExerciseViewer = require('./ExerciseViewer'),
    events = require('duplex-emitter')( shoe( EVENTS_ENDPOINT ) ),
    exerciseEvents = eventEmitter({}),
    radio = eventEmitter({}),

    exerciseTmp = require('../templates/exercise.hbs'),
    indexTmp = require('../templates/index.hbs'),

    state = {},
    timer,
    viewer

$.get( '/user', function( userId ) {
    var storedId = localStorage.userId,
        syncId
    if ( storedId ) {
        syncId = storedId
    } else {
        syncId = userId
        localStorage.userId = userId
    }
    events.emit( 'sync', syncId )
})

/**
 * @param {String} eventType the type of event (step, halt, ding)
 * @param {Function} done the function to call when the transition has completed
 */
function triggerExerciseEvent( eventType, done ) {
    return function() {
        var args = Array.prototype.slice.call( arguments )
        exerciseEvents.emit.apply( exerciseEvents, [ eventType ].concat( args, done ) )
    }
}

function toTimeStr( msec ) {
    if ( msec === Infinity ) {
        return '∞'
    }

    var LAG_COMPENSATION = 400,
        MSEC_IN_MIN = 60 * 1000,
        SEC_IN_MSEC = 1000,
        minutesStr = Math.floor( ( msec + LAG_COMPENSATION ) / MSEC_IN_MIN ),
        secondsRemaining = Math.round( ( msec + LAG_COMPENSATION ) % MSEC_IN_MIN / SEC_IN_MSEC ),
        secondsStr = ( secondsRemaining < 10 ? '0' : '' ) + secondsRemaining

    return minutesStr + ':' + secondsStr
}

function Timer() {}

Timer.prototype = {
    _update: function() {
        if ( this.timeRemaining === 0 ) {
            return this.ding()
        }

        if ( this.timeRemaining <= 10 * 1000 ) {
            this._timer.addClass('stress')
        }

        this._timer.html( toTimeStr( Math.max( this.timeRemaining, 0 ) ) )
        this.timeRemaining = Math.max( this.timeRemaining - 1000, 0 )
    },
    start: function( timeRemaining ) {
        this._stopped = false
        this._timer = $('.timer')
        this.timeRemaining = timeRemaining || Infinity
        this._update()
        if ( this.timeRemaining < Infinity ) {
            this.timerInterval = setInterval( this._update.bind( this ), 1000 )
        }
        this._timer.addClass('active')
    },
    /** actually stops the timer */
    _stop: function() {
        this._stopped = true
        clearInterval( this.timerInterval )
    },
    /** these two stop the timer and add the appropriate styles */
    stop: function() {
        if ( !this._stopped ) {
            this._stop()
            this._timer.removeClass('active').addClass('stopped')
        }
    },
    ding: function() {
        this._stop()
        this._timer.html('0:00').addClass('stress').addClass('dinged')
    }
}

function renderExerciseView( exerciseName, conf, user ) {
    var stepIndex = 1,
        steps = _.map( conf.steps, function( stateDesc, stateName ) {
            return {
                name: stateName,
                desc: stateDesc
            }
        }),
        mac = hmac( user.id + exerciseName, user.key ).toString().substring( 0, 6 ),
        cloneUrl = 'http://' + window.location.host + '/repos/' +
            user.id + '/' + mac + '/' + exerciseName + '.git',
        templateParams = {
            title: conf.title,
            cloneUrl: cloneUrl,
            steps: steps,
            stepIndex: function() {
                return stepIndex++
            },
            timeLimit: toTimeStr( conf.timeLimit * 1000 ), // sec -> msec
            exerciseName: exerciseName
        }

    return $( exerciseTmp( templateParams ) )
}

function selectViewStep( name ) {
    return $('.exercise-view').find( '[data-statename="' + name + '"]' )
}

function hashChangeExercise() {
    radio.emit( 'exerciseChanged', window.location.hash.substring(1) )
}

function changeHashSilent( newHash ) {
    $(window).off( 'hashchange', hashChangeExercise )
    window.location.hash = newHash
    setTimeout( function() {
        $(window).on( 'hashchange', hashChangeExercise )
    }, 0 )
}

radio.on( 'exerciseChanged', function( changeTo ) {
    var exerciseViewerConf,
        exerciseView,
        newExercise,
        silent,
        setHash

    if ( changeTo.newExercise ) {
        newExercise = changeTo.newExercise
        silent = changeTo.silent
        setHash = changeTo.setHash
    } else {
        newExercise = changeTo
    }

    require('event-emitter/all-off')( exerciseEvents )
    exerciseEvents = eventEmitter({})

    if ( !silent ) {
        events.emit( 'exerciseChanged', newExercise )
        delete state.exerciseState
    }

    if ( setHash ) { changeHashSilent( newExercise ) }

    if ( exercises[ newExercise ] ) {
        exerciseViewerConf = exercises[ newExercise ]()
        exerciseView = renderExerciseView( newExercise, exerciseViewerConf, state.user )

        $('.main-content').html( exerciseView )

        if ( state.exerciseState ) {
            selectViewStep( state.exerciseState, exerciseView ).addClass('focused')
            timer = new Timer()
            timer.start( state.timeRemaining )
            $('.exercise-steps').toggleClass( 'focused', true )
            $('.step-number').toggleClass( 'blurred', true )
            $('.step-desc').toggleClass( 'blurred', true )
        }

        viewer = new ExerciseViewer( exerciseViewerConf.feedback, exerciseEvents )
    } else {
        changeHashSilent('')
        $('.main-content').html( indexTmp({ desc: exercises._order.map( function( exercise ) {
            return { title: exercises[ exercise ]().title, name: exercise }
        }) }) )
    }

    $('.main-content').removeClass('hide')
})

$(window).on( 'hashchange', hashChangeExercise )

events.on( 'sync', function( newState ) {
    var hashExercise = window.location.hash.substring(1)

    /* merge the server's state with the client state
       only overwriting if new (non-null) value or endTime is received */
    _.forOwn( newState, function( v, k ) {
        state[k] = ( v === 'null' || !v ? state[k] : v )
        if ( k === 'endTime' ) {
            state[k] = v
        }
    })

    radio.emit( 'exerciseChanged', {
        newExercise: hashExercise,
        silent: state.currentExercise === hashExercise,
        setHash: true
    })
})

// forward exercise events to exercise machine emitter
events.on( 'step', triggerExerciseEvent( 'step', function( newState, oldState, stepOutput ) {
    var newStateStepView = selectViewStep( newState ),
        newStateFeedback = newStateStepView.find('.feedback'),
        exerciseSteps = $('.exercise-view').find('.exercise-step')

    if ( !newStateStepView.length ) { return }

    exerciseSteps.removeClass('focused issue').find('.feedback').html('')

    newStateStepView.addClass('focused')
    if ( stepOutput ) {
        newStateStepView.addClass('issue')
        newStateFeedback.html( stepOutput )
        newStateFeedback.addClass('flash')
        setTimeout( function() {
            newStateFeedback.removeClass('flash')
        }, 70 )
    }
}) )
events.on( 'halt', triggerExerciseEvent( 'halt', function() {
    if ( timer ) {
        timer.stop()
    }
    state.endTime = undefined
}) )
events.on( 'ding', triggerExerciseEvent( 'ding', function() {
    if ( timer ) {
        timer.ding()
    }
    $('.exercise-view').find('.exercise-step').removeClass('focused')
    state.endTime = undefined
}) )

window.resetId = function() {
    localStorage.clear('userId')
    window.location.reload()
}
<commit_msg>Always update timeRemaining on sync<commit_after>'use strict'

var EVENTS_ENDPOINT = '/events',
    shoe = require('shoe'),
    $ = require('zeptojs'),
    _ = require('lodash'),
    hmac = require('crypto-js/hmac-sha1'),
    eventEmitter = require('event-emitter'),
    exercises = require('gitstream-exercises/viewers'),
    ExerciseViewer = require('./ExerciseViewer'),
    events = require('duplex-emitter')( shoe( EVENTS_ENDPOINT ) ),
    exerciseEvents = eventEmitter({}),
    radio = eventEmitter({}),

    exerciseTmp = require('../templates/exercise.hbs'),
    indexTmp = require('../templates/index.hbs'),

    state = {},
    timer,
    viewer

$.get( '/user', function( userId ) {
    var storedId = localStorage.userId,
        syncId
    if ( storedId ) {
        syncId = storedId
    } else {
        syncId = userId
        localStorage.userId = userId
    }
    events.emit( 'sync', syncId )
})

/**
 * @param {String} eventType the type of event (step, halt, ding)
 * @param {Function} done the function to call when the transition has completed
 */
function triggerExerciseEvent( eventType, done ) {
    return function() {
        var args = Array.prototype.slice.call( arguments )
        exerciseEvents.emit.apply( exerciseEvents, [ eventType ].concat( args, done ) )
    }
}

function toTimeStr( msec ) {
    if ( msec === Infinity ) {
        return '∞'
    }

    var LAG_COMPENSATION = 400,
        MSEC_IN_MIN = 60 * 1000,
        SEC_IN_MSEC = 1000,
        minutesStr = Math.floor( ( msec + LAG_COMPENSATION ) / MSEC_IN_MIN ),
        secondsRemaining = Math.round( ( msec + LAG_COMPENSATION ) % MSEC_IN_MIN / SEC_IN_MSEC ),
        secondsStr = ( secondsRemaining < 10 ? '0' : '' ) + secondsRemaining

    return minutesStr + ':' + secondsStr
}

function Timer() {}

Timer.prototype = {
    _update: function() {
        if ( this.timeRemaining === 0 ) {
            return this.ding()
        }

        if ( this.timeRemaining <= 10 * 1000 ) {
            this._timer.addClass('stress')
        }

        this._timer.html( toTimeStr( Math.max( this.timeRemaining, 0 ) ) )
        this.timeRemaining = Math.max( this.timeRemaining - 1000, 0 )
    },
    start: function( timeRemaining ) {
        this._stopped = false
        this._timer = $('.timer')
        this.timeRemaining = timeRemaining || Infinity
        this._update()
        if ( this.timeRemaining < Infinity ) {
            this.timerInterval = setInterval( this._update.bind( this ), 1000 )
        }
        this._timer.addClass('active')
    },
    /** actually stops the timer */
    _stop: function() {
        this._stopped = true
        clearInterval( this.timerInterval )
    },
    /** these two stop the timer and add the appropriate styles */
    stop: function() {
        if ( !this._stopped ) {
            this._stop()
            this._timer.removeClass('active').addClass('stopped')
        }
    },
    ding: function() {
        this._stop()
        this._timer.html('0:00').addClass('stress').addClass('dinged')
    }
}

function renderExerciseView( exerciseName, conf, user ) {
    var stepIndex = 1,
        steps = _.map( conf.steps, function( stateDesc, stateName ) {
            return {
                name: stateName,
                desc: stateDesc
            }
        }),
        mac = hmac( user.id + exerciseName, user.key ).toString().substring( 0, 6 ),
        cloneUrl = 'http://' + window.location.host + '/repos/' +
            user.id + '/' + mac + '/' + exerciseName + '.git',
        templateParams = {
            title: conf.title,
            cloneUrl: cloneUrl,
            steps: steps,
            stepIndex: function() {
                return stepIndex++
            },
            timeLimit: toTimeStr( conf.timeLimit * 1000 ), // sec -> msec
            exerciseName: exerciseName
        }

    return $( exerciseTmp( templateParams ) )
}

function selectViewStep( name ) {
    return $('.exercise-view').find( '[data-statename="' + name + '"]' )
}

function hashChangeExercise() {
    radio.emit( 'exerciseChanged', window.location.hash.substring(1) )
}

function changeHashSilent( newHash ) {
    $(window).off( 'hashchange', hashChangeExercise )
    window.location.hash = newHash
    setTimeout( function() {
        $(window).on( 'hashchange', hashChangeExercise )
    }, 0 )
}

radio.on( 'exerciseChanged', function( changeTo ) {
    var exerciseViewerConf,
        exerciseView,
        newExercise,
        silent,
        setHash

    if ( changeTo.newExercise ) {
        newExercise = changeTo.newExercise
        silent = changeTo.silent
        setHash = changeTo.setHash
    } else {
        newExercise = changeTo
    }

    require('event-emitter/all-off')( exerciseEvents )
    exerciseEvents = eventEmitter({})

    if ( !silent ) {
        events.emit( 'exerciseChanged', newExercise )
        delete state.exerciseState
    }

    if ( setHash ) { changeHashSilent( newExercise ) }

    if ( exercises[ newExercise ] ) {
        exerciseViewerConf = exercises[ newExercise ]()
        exerciseView = renderExerciseView( newExercise, exerciseViewerConf, state.user )

        $('.main-content').html( exerciseView )

        if ( state.exerciseState ) {
            selectViewStep( state.exerciseState, exerciseView ).addClass('focused')
            timer = new Timer()
            timer.start( state.timeRemaining )
            $('.exercise-steps').toggleClass( 'focused', true )
            $('.step-number').toggleClass( 'blurred', true )
            $('.step-desc').toggleClass( 'blurred', true )
        }

        viewer = new ExerciseViewer( exerciseViewerConf.feedback, exerciseEvents )
    } else {
        changeHashSilent('')
        $('.main-content').html( indexTmp({ desc: exercises._order.map( function( exercise ) {
            return { title: exercises[ exercise ]().title, name: exercise }
        }) }) )
    }

    $('.main-content').removeClass('hide')
})

$(window).on( 'hashchange', hashChangeExercise )

events.on( 'sync', function( newState ) {
    var hashExercise = window.location.hash.substring(1)

    /* merge the server's state with the client state
       only overwriting if new (non-null) value, endTime,
       or timeRemaining is received */
    _.forOwn( newState, function( v, k ) {
        state[k] = ( v === 'null' || !v ? state[k] : v )
        if ( k === 'endTime' || k === 'timeRemaining' ) {
            state[k] = v
        }
    })

    radio.emit( 'exerciseChanged', {
        newExercise: hashExercise,
        silent: state.currentExercise === hashExercise,
        setHash: true
    })
})

// forward exercise events to exercise machine emitter
events.on( 'step', triggerExerciseEvent( 'step', function( newState, oldState, stepOutput ) {
    var newStateStepView = selectViewStep( newState ),
        newStateFeedback = newStateStepView.find('.feedback'),
        exerciseSteps = $('.exercise-view').find('.exercise-step')

    if ( !newStateStepView.length ) { return }

    exerciseSteps.removeClass('focused issue').find('.feedback').html('')

    newStateStepView.addClass('focused')
    if ( stepOutput ) {
        newStateStepView.addClass('issue')
        newStateFeedback.html( stepOutput )
        newStateFeedback.addClass('flash')
        setTimeout( function() {
            newStateFeedback.removeClass('flash')
        }, 70 )
    }
}) )
events.on( 'halt', triggerExerciseEvent( 'halt', function() {
    if ( timer ) {
        timer.stop()
    }
    state.endTime = undefined
}) )
events.on( 'ding', triggerExerciseEvent( 'ding', function() {
    if ( timer ) {
        timer.ding()
    }
    $('.exercise-view').find('.exercise-step').removeClass('focused')
    state.endTime = undefined
}) )

window.resetId = function() {
    localStorage.clear('userId')
    window.location.reload()
}
<|endoftext|>


<commit_before>angular.module('promise-queue', [])
  .factory('PromiseQueue', function($q) {

    var PromiseQueue = function (){
      this._queue = [];
      this._pause = false;
    }

    /**
     * Adds a function to the queue. It optionally accepts an array of functions
     * @name PromiseQueue#add
     * @param {function} func function OR array of functions
     * @returns {undefined}
     */
    PromiseQueue.prototype.add = function(func, instant) {
      var instant = instant || false;

      if(Array.isArray(func)) {
        this._queue = this._queue.concat(func);
      }else if(typeof func === 'function') {

        if(instant){
          this._queue.unshift(func);
        } else {
          this._queue.push(func);
        }

      }else{
        throw new Error('No functions provided');
      }

      return this;

    };

    /**
     * Starts the queue
     *
     * @name PromiseQueue#start
     * @returns {undefined}
     */
    PromiseQueue.prototype.start = function() {
      this._pause = false;
      this.next();

      return this;
    };

    /**
     * Drains the queue
     * The queue will be emptied. Any function currently in progress will be allowed to finish.
     *
     * @name PromiseQueue#play
     * @returns {undefined}
     */
    PromiseQueue.prototype.drain = function() {
      this._queue = [];

      return this;
    };

    /**
     * Instant
     * Puts the included function in the front of the queue
     *
     * @name PromiseQueue#instant
      * @param {function} func function OR array of functions
     * @returns {undefined}
     */
    PromiseQueue.prototype.instant = function(func) {
      this.add(func, true);

      return this;
    };

    /**
     * Remove
     * Compares the provided function with all functions in the queue and removes them
     *
     * @name PromiseQueue#remove
      * @param {function} func function OR array of functions
     * @returns {undefined}
     */
    PromiseQueue.prototype.remove = function(func) {
      for(var i = 0; i < this._queue.length; i++) {
        if(func === this._queue[i]) {
          this._queue.splice(i, 1);
        }
      }

      return this;
    };

    /**
     * Pauses the queue
     *
     * @name PromiseQueue#pause
     * @returns {undefined}
     */
    PromiseQueue.prototype.pause = function() {
      this._pause = true;

      return this;
    };

    /**
     * Recursive function that wraps the next function in the queue in a promise and invokes it.
     * The next function will not be invoked if the queue has been paused.
     * The queue will loop to the first function once it has reached the end of the queue
     *
     * @name PromiseQueue#next
     * @returns {undefined}
     */
    PromiseQueue.prototype.next = function() {
      var self = this;

      if(self._pause === true) return;
      if(self._queue.length === 0) return;

      var func = self._queue.shift();

      promisify(func)
        .then(function() {
          self.next();
        })

    };

    // Utility function that creates a new deferred
    // and provides an anonymous function that resolves the deferred
    // as the first argument of the provided function
    function promisify(func) {
      var deferred = $q.defer();

      func(function(){
        deferred.resolve();
      });

      return deferred.promise;
    }

    return PromiseQueue;

  });<commit_msg>correct js hinting issues<commit_after>angular.module('promise-queue', [])
  .factory('PromiseQueue', function($q) {

    var PromiseQueue = function (){
      this._queue = [];
      this._pause = false;
    };

    /**
     * Adds a function to the queue. It optionally accepts an array of functions
     * @name PromiseQueue#add
     * @param {function} func function OR array of functions
     * @returns {undefined}
     */
    PromiseQueue.prototype.add = function(func, instant) {
      instant = instant || false;

      if(Array.isArray(func)) {
        this._queue = this._queue.concat(func);
      }else if(typeof func === 'function') {

        if(instant){
          this._queue.unshift(func);
        } else {
          this._queue.push(func);
        }

      }else{
        throw new Error('No functions provided');
      }

      return this;

    };

    /**
     * Starts the queue
     *
     * @name PromiseQueue#start
     * @returns {undefined}
     */
    PromiseQueue.prototype.start = function() {
      this._pause = false;
      this.next();

      return this;
    };

    /**
     * Drains the queue
     * The queue will be emptied. Any function currently in progress will be allowed to finish.
     *
     * @name PromiseQueue#play
     * @returns {undefined}
     */
    PromiseQueue.prototype.drain = function() {
      this._queue = [];

      return this;
    };

    /**
     * Instant
     * Puts the included function in the front of the queue
     *
     * @name PromiseQueue#instant
      * @param {function} func function OR array of functions
     * @returns {undefined}
     */
    PromiseQueue.prototype.instant = function(func) {
      this.add(func, true);

      return this;
    };

    /**
     * Remove
     * Compares the provided function with all functions in the queue and removes them
     *
     * @name PromiseQueue#remove
      * @param {function} func function OR array of functions
     * @returns {undefined}
     */
    PromiseQueue.prototype.remove = function(func) {
      for(var i = 0; i < this._queue.length; i++) {
        if(func === this._queue[i]) {
          this._queue.splice(i, 1);
        }
      }

      return this;
    };

    /**
     * Pauses the queue
     *
     * @name PromiseQueue#pause
     * @returns {undefined}
     */
    PromiseQueue.prototype.pause = function() {
      this._pause = true;

      return this;
    };

    /**
     * Recursive function that wraps the next function in the queue in a promise and invokes it.
     * The next function will not be invoked if the queue has been paused.
     * The queue will loop to the first function once it has reached the end of the queue
     *
     * @name PromiseQueue#next
     * @returns {undefined}
     */
    PromiseQueue.prototype.next = function() {
      var self = this;

      if(self._pause === true) return;
      if(self._queue.length === 0) return;

      var func = self._queue.shift();

      promisify(func)
        .then(function() {
          self.next();
        });

    };

    // Utility function that creates a new deferred
    // and provides an anonymous function that resolves the deferred
    // as the first argument of the provided function
    function promisify(func) {
      var deferred = $q.defer();

      func(function(){
        deferred.resolve();
      });

      return deferred.promise;
    }

    return PromiseQueue;

  });<|endoftext|>