<commit_before>require('ember-metal/core');
require('ember-metal/platform');
require('ember-metal/utils');
require('ember-metal/properties');
require('ember-metal/watching');

/**
@module ember-metal
*/

Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);

var get = Ember.get,
    set = Ember.set,
    metaFor = Ember.meta,
    guidFor = Ember.guidFor,
    a_slice = [].slice,
    o_create = Ember.create,
    META_KEY = Ember.META_KEY,
    watch = Ember.watch,
    unwatch = Ember.unwatch;

// ..........................................................
// DEPENDENT KEYS
//

// data structure:
//  meta.deps = {
//   'depKey': {
//     'keyName': count,
//   }
//  }

/*
  This function returns a map of unique dependencies for a
  given object and key.
*/
function keysForDep(obj, depsMeta, depKey) {
  var keys = depsMeta[depKey];
  if (!keys) {
    // if there are no dependencies yet for a the given key
    // create a new empty list of dependencies for the key
    keys = depsMeta[depKey] = {};
  } else if (!depsMeta.hasOwnProperty(depKey)) {
    // otherwise if the dependency list is inherited from
    // a superclass, clone the hash
    keys = depsMeta[depKey] = o_create(keys);
  }
  return keys;
}

/* return obj[META_KEY].deps */
function metaForDeps(obj, meta) {
  return keysForDep(obj, meta, 'deps');
}

function addDependentKeys(desc, obj, keyName, meta) {
  // the descriptor has a list of dependent keys, so
  // add all of its dependent keys.
  var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
  if (!depKeys) return;

  depsMeta = metaForDeps(obj, meta);

  for(idx = 0, len = depKeys.length; idx < len; idx++) {
    depKey = depKeys[idx];
    // Lookup keys meta for depKey
    keys = keysForDep(obj, depsMeta, depKey);
    // Increment the number of times depKey depends on keyName.
    keys[keyName] = (keys[keyName] || 0) + 1;
    // Watch the depKey
    watch(obj, depKey);
  }
}

function removeDependentKeys(desc, obj, keyName, meta) {
  // the descriptor has a list of dependent keys, so
  // add all of its dependent keys.
  var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
  if (!depKeys) return;

  depsMeta = metaForDeps(obj, meta);

  for(idx = 0, len = depKeys.length; idx < len; idx++) {
    depKey = depKeys[idx];
    // Lookup keys meta for depKey
    keys = keysForDep(obj, depsMeta, depKey);
    // Increment the number of times depKey depends on keyName.
    keys[keyName] = (keys[keyName] || 0) - 1;
    // Watch the depKey
    unwatch(obj, depKey);
  }
}

// ..........................................................
// COMPUTED PROPERTY
//

/**
  @class ComputedProperty
  @namespace Ember
  @extends Ember.Descriptor
  @constructor
*/
function ComputedProperty(func, opts) {
  this.func = func;
  this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
  this._dependentKeys = opts && opts.dependentKeys;
}

Ember.ComputedProperty = ComputedProperty;
ComputedProperty.prototype = new Ember.Descriptor();

var ComputedPropertyPrototype = ComputedProperty.prototype;

/**
  Call on a computed property to set it into cacheable mode. When in this
  mode the computed property will automatically cache the return value of
  your function until one of the dependent keys changes.

  ```javascript
  MyApp.president = Ember.Object.create({
    fullName: function() {
      return this.get('firstName') + ' ' + this.get('lastName');

      // After calculating the value of this function, Ember will
      // return that value without re-executing this function until
      // one of the dependent properties change.
    }.property('firstName', 'lastName')
  });
  ```

  Properties are cacheable by default.

  @method cacheable
  @param {Boolean} aFlag optional set to `false` to disable caching
  @chainable
*/
ComputedPropertyPrototype.cacheable = function(aFlag) {
  this._cacheable = aFlag !== false;
  return this;
};

/**
  Call on a computed property to set it into non-cached mode. When in this
  mode the computed property will not automatically cache the return value.

  ```javascript
  MyApp.outsideService = Ember.Object.create({
    value: function() {
      return OutsideService.getValue();
    }.property().volatile()
  });
  ```

  @method volatile
  @chainable
*/
ComputedPropertyPrototype.volatile = function() {
  return this.cacheable(false);
};

/**
  Sets the dependent keys on this computed property. Pass any number of
  arguments containing key paths that this computed property depends on.

  ```javascript
  MyApp.president = Ember.Object.create({
    fullName: Ember.computed(function() {
      return this.get('firstName') + ' ' + this.get('lastName');

      // Tell Ember that this computed property depends on firstName
      // and lastName
    }).property('firstName', 'lastName')
  });
  ```

  @method property
  @param {String} path* zero or more property paths
  @chainable
*/
ComputedPropertyPrototype.property = function() {
  var args = [];
  for (var i = 0, l = arguments.length; i < l; i++) {
    args.push(arguments[i]);
  }
  this._dependentKeys = args;
  return this;
};

/**
  In some cases, you may want to annotate computed properties with additional
  metadata about how they function or what values they operate on. For example,
  computed property functions may close over variables that are then no longer
  available for introspection.

  You can pass a hash of these values to a computed property like this:

  ```
  person: function() {
    var personId = this.get('personId');
    return App.Person.create({ id: personId });
  }.property().meta({ type: App.Person })
  ```

  The hash that you pass to the `meta()` function will be saved on the
  computed property descriptor under the `_meta` key. Ember runtime
  exposes a public API for retrieving these values from classes,
  via the `metaForProperty()` function.

  @method meta
  @param {Hash} meta
  @chainable
*/

ComputedPropertyPrototype.meta = function(meta) {
  if (arguments.length === 0) {
    return this._meta || {};
  } else {
    this._meta = meta;
    return this;
  }
};

/* impl descriptor API */
ComputedPropertyPrototype.willWatch = function(obj, keyName) {
  // watch already creates meta for this instance
  var meta = obj[META_KEY];
  Ember.assert('watch should have setup meta to be writable', meta.source === obj);
  if (!(keyName in meta.cache)) {
    addDependentKeys(this, obj, keyName, meta);
  }
};

ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
  var meta = obj[META_KEY];
  Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
  if (!(keyName in meta.cache)) {
    // unwatch already creates meta for this instance
    removeDependentKeys(this, obj, keyName, meta);
  }
};

/* impl descriptor API */
ComputedPropertyPrototype.didChange = function(obj, keyName) {
  // _suspended is set via a CP.set to ensure we don't clear
  // the cached value set by the setter
  if (this._cacheable && this._suspended !== obj) {
    var meta = metaFor(obj);
    if (keyName in meta.cache) {
      delete meta.cache[keyName];
      if (!meta.watching[keyName]) {
        removeDependentKeys(this, obj, keyName, meta);
      }
    }
  }
};

/* impl descriptor API */
ComputedPropertyPrototype.get = function(obj, keyName) {
  var ret, cache, meta;
  if (this._cacheable) {
    meta = metaFor(obj);
    cache = meta.cache;
    if (keyName in cache) { return cache[keyName]; }
    ret = cache[keyName] = this.func.call(obj, keyName);
    if (!meta.watching[keyName]) {
      addDependentKeys(this, obj, keyName, meta);
    }
  } else {
    ret = this.func.call(obj, keyName);
  }
  return ret;
};

/* impl descriptor API */
ComputedPropertyPrototype.set = function(obj, keyName, value) {
  var cacheable = this._cacheable,
      func = this.func,
      meta = metaFor(obj, cacheable),
      watched = meta.watching[keyName],
      oldSuspended = this._suspended,
      hadCachedValue = false,
      cache = meta.cache,
      cachedValue, ret;

  this._suspended = obj;

  try {
    if (cacheable && cache.hasOwnProperty(keyName)) {
      cachedValue = cache[keyName];
      hadCachedValue = true;
    }

    // Check if the CP has been wrapped
    if (func.wrappedFunction) { func = func.wrappedFunction; }

    // For backwards-compatibility with computed properties
    // that check for arguments.length === 2 to determine if
    // they are being get or set, only pass the old cached
    // value if the computed property opts into a third
    // argument.
    if (func.length === 3) {
      ret = func.call(obj, keyName, value, cachedValue);
    } else if (func.length === 2) {
      ret = func.call(obj, keyName, value);
    } else {
      Ember.defineProperty(obj, keyName, null, cachedValue);
      Ember.set(obj, keyName, value);
      return;
    }

    if (hadCachedValue && cachedValue === ret) { return; }

    if (watched) { Ember.propertyWillChange(obj, keyName); }

    if (hadCachedValue) {
      delete cache[keyName];
    }

    if (cacheable) {
      if (!watched && !hadCachedValue) {
        addDependentKeys(this, obj, keyName, meta);
      }
      cache[keyName] = ret;
    }

    if (watched) { Ember.propertyDidChange(obj, keyName); }
  } finally {
    this._suspended = oldSuspended;
  }
  return ret;
};

/* called when property is defined */
ComputedPropertyPrototype.setup = function(obj, keyName) {
  var meta = obj[META_KEY];
  if (meta && meta.watching[keyName]) {
    addDependentKeys(this, obj, keyName, metaFor(obj));
  }
};

/* called before property is overridden */
ComputedPropertyPrototype.teardown = function(obj, keyName) {
  var meta = metaFor(obj);

  if (meta.watching[keyName] || keyName in meta.cache) {
    removeDependentKeys(this, obj, keyName, meta);
  }

  if (this._cacheable) { delete meta.cache[keyName]; }

  return null; // no value to restore
};

/**
  This helper returns a new property descriptor that wraps the passed
  computed property function. You can use this helper to define properties
  with mixins or via `Ember.defineProperty()`.

  The function you pass will be used to both get and set property values.
  The function should accept two parameters, key and value. If value is not
  undefined you should set the value first. In either case return the
  current value of the property.

  @method computed
  @for Ember
  @param {Function} func The computed property function.
  @return {Ember.ComputedProperty} property descriptor instance
*/
Ember.computed = function(func) {
  var args;

  if (arguments.length > 1) {
    args = a_slice.call(arguments, 0, -1);
    func = a_slice.call(arguments, -1)[0];
  }

  var cp = new ComputedProperty(func);

  if (args) {
    cp.property.apply(cp, args);
  }

  return cp;
};

/**
  Returns the cached value for a property, if one exists.
  This can be useful for peeking at the value of a computed
  property that is generated lazily, without accidentally causing
  it to be created.

  @method cacheFor
  @for Ember
  @param {Object} obj the object whose property you want to check
  @param {String} key the name of the property whose cached value you want
    to return
*/
Ember.cacheFor = function cacheFor(obj, key) {
  var cache = metaFor(obj, false).cache;

  if (cache && key in cache) {
    return cache[key];
  }
};

/**
  @method computed.not
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.not = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    return !get(this, dependentKey);
  });
};

/**
  @method computed.none
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.none = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    var val = get(this, dependentKey);
    return Ember.isNone(val);
  });
};

/**
  @method computed.empty
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.empty = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    var val = get(this, dependentKey);
    return Ember.isEmpty(val);
  });
};

/**
  @method computed.bool
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.bool = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    return !!get(this, dependentKey);
  });
};

/**
  @method computed.alias
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.alias = function(dependentKey) {
  return Ember.computed(dependentKey, function(key, value){
    if (arguments.length === 1) {
      return get(this, dependentKey);
    } else {
      set(this, dependentKey, value);
      return value;
    }
  });
};
<commit_msg>Remove old comment<commit_after>require('ember-metal/core');
require('ember-metal/platform');
require('ember-metal/utils');
require('ember-metal/properties');
require('ember-metal/watching');

/**
@module ember-metal
*/

Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);

var get = Ember.get,
    set = Ember.set,
    metaFor = Ember.meta,
    guidFor = Ember.guidFor,
    a_slice = [].slice,
    o_create = Ember.create,
    META_KEY = Ember.META_KEY,
    watch = Ember.watch,
    unwatch = Ember.unwatch;

// ..........................................................
// DEPENDENT KEYS
//

// data structure:
//  meta.deps = {
//   'depKey': {
//     'keyName': count,
//   }
//  }

/*
  This function returns a map of unique dependencies for a
  given object and key.
*/
function keysForDep(obj, depsMeta, depKey) {
  var keys = depsMeta[depKey];
  if (!keys) {
    // if there are no dependencies yet for a the given key
    // create a new empty list of dependencies for the key
    keys = depsMeta[depKey] = {};
  } else if (!depsMeta.hasOwnProperty(depKey)) {
    // otherwise if the dependency list is inherited from
    // a superclass, clone the hash
    keys = depsMeta[depKey] = o_create(keys);
  }
  return keys;
}

function metaForDeps(obj, meta) {
  return keysForDep(obj, meta, 'deps');
}

function addDependentKeys(desc, obj, keyName, meta) {
  // the descriptor has a list of dependent keys, so
  // add all of its dependent keys.
  var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
  if (!depKeys) return;

  depsMeta = metaForDeps(obj, meta);

  for(idx = 0, len = depKeys.length; idx < len; idx++) {
    depKey = depKeys[idx];
    // Lookup keys meta for depKey
    keys = keysForDep(obj, depsMeta, depKey);
    // Increment the number of times depKey depends on keyName.
    keys[keyName] = (keys[keyName] || 0) + 1;
    // Watch the depKey
    watch(obj, depKey);
  }
}

function removeDependentKeys(desc, obj, keyName, meta) {
  // the descriptor has a list of dependent keys, so
  // add all of its dependent keys.
  var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
  if (!depKeys) return;

  depsMeta = metaForDeps(obj, meta);

  for(idx = 0, len = depKeys.length; idx < len; idx++) {
    depKey = depKeys[idx];
    // Lookup keys meta for depKey
    keys = keysForDep(obj, depsMeta, depKey);
    // Increment the number of times depKey depends on keyName.
    keys[keyName] = (keys[keyName] || 0) - 1;
    // Watch the depKey
    unwatch(obj, depKey);
  }
}

// ..........................................................
// COMPUTED PROPERTY
//

/**
  @class ComputedProperty
  @namespace Ember
  @extends Ember.Descriptor
  @constructor
*/
function ComputedProperty(func, opts) {
  this.func = func;
  this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
  this._dependentKeys = opts && opts.dependentKeys;
}

Ember.ComputedProperty = ComputedProperty;
ComputedProperty.prototype = new Ember.Descriptor();

var ComputedPropertyPrototype = ComputedProperty.prototype;

/**
  Call on a computed property to set it into cacheable mode. When in this
  mode the computed property will automatically cache the return value of
  your function until one of the dependent keys changes.

  ```javascript
  MyApp.president = Ember.Object.create({
    fullName: function() {
      return this.get('firstName') + ' ' + this.get('lastName');

      // After calculating the value of this function, Ember will
      // return that value without re-executing this function until
      // one of the dependent properties change.
    }.property('firstName', 'lastName')
  });
  ```

  Properties are cacheable by default.

  @method cacheable
  @param {Boolean} aFlag optional set to `false` to disable caching
  @chainable
*/
ComputedPropertyPrototype.cacheable = function(aFlag) {
  this._cacheable = aFlag !== false;
  return this;
};

/**
  Call on a computed property to set it into non-cached mode. When in this
  mode the computed property will not automatically cache the return value.

  ```javascript
  MyApp.outsideService = Ember.Object.create({
    value: function() {
      return OutsideService.getValue();
    }.property().volatile()
  });
  ```

  @method volatile
  @chainable
*/
ComputedPropertyPrototype.volatile = function() {
  return this.cacheable(false);
};

/**
  Sets the dependent keys on this computed property. Pass any number of
  arguments containing key paths that this computed property depends on.

  ```javascript
  MyApp.president = Ember.Object.create({
    fullName: Ember.computed(function() {
      return this.get('firstName') + ' ' + this.get('lastName');

      // Tell Ember that this computed property depends on firstName
      // and lastName
    }).property('firstName', 'lastName')
  });
  ```

  @method property
  @param {String} path* zero or more property paths
  @chainable
*/
ComputedPropertyPrototype.property = function() {
  var args = [];
  for (var i = 0, l = arguments.length; i < l; i++) {
    args.push(arguments[i]);
  }
  this._dependentKeys = args;
  return this;
};

/**
  In some cases, you may want to annotate computed properties with additional
  metadata about how they function or what values they operate on. For example,
  computed property functions may close over variables that are then no longer
  available for introspection.

  You can pass a hash of these values to a computed property like this:

  ```
  person: function() {
    var personId = this.get('personId');
    return App.Person.create({ id: personId });
  }.property().meta({ type: App.Person })
  ```

  The hash that you pass to the `meta()` function will be saved on the
  computed property descriptor under the `_meta` key. Ember runtime
  exposes a public API for retrieving these values from classes,
  via the `metaForProperty()` function.

  @method meta
  @param {Hash} meta
  @chainable
*/

ComputedPropertyPrototype.meta = function(meta) {
  if (arguments.length === 0) {
    return this._meta || {};
  } else {
    this._meta = meta;
    return this;
  }
};

/* impl descriptor API */
ComputedPropertyPrototype.willWatch = function(obj, keyName) {
  // watch already creates meta for this instance
  var meta = obj[META_KEY];
  Ember.assert('watch should have setup meta to be writable', meta.source === obj);
  if (!(keyName in meta.cache)) {
    addDependentKeys(this, obj, keyName, meta);
  }
};

ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
  var meta = obj[META_KEY];
  Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
  if (!(keyName in meta.cache)) {
    // unwatch already creates meta for this instance
    removeDependentKeys(this, obj, keyName, meta);
  }
};

/* impl descriptor API */
ComputedPropertyPrototype.didChange = function(obj, keyName) {
  // _suspended is set via a CP.set to ensure we don't clear
  // the cached value set by the setter
  if (this._cacheable && this._suspended !== obj) {
    var meta = metaFor(obj);
    if (keyName in meta.cache) {
      delete meta.cache[keyName];
      if (!meta.watching[keyName]) {
        removeDependentKeys(this, obj, keyName, meta);
      }
    }
  }
};

/* impl descriptor API */
ComputedPropertyPrototype.get = function(obj, keyName) {
  var ret, cache, meta;
  if (this._cacheable) {
    meta = metaFor(obj);
    cache = meta.cache;
    if (keyName in cache) { return cache[keyName]; }
    ret = cache[keyName] = this.func.call(obj, keyName);
    if (!meta.watching[keyName]) {
      addDependentKeys(this, obj, keyName, meta);
    }
  } else {
    ret = this.func.call(obj, keyName);
  }
  return ret;
};

/* impl descriptor API */
ComputedPropertyPrototype.set = function(obj, keyName, value) {
  var cacheable = this._cacheable,
      func = this.func,
      meta = metaFor(obj, cacheable),
      watched = meta.watching[keyName],
      oldSuspended = this._suspended,
      hadCachedValue = false,
      cache = meta.cache,
      cachedValue, ret;

  this._suspended = obj;

  try {
    if (cacheable && cache.hasOwnProperty(keyName)) {
      cachedValue = cache[keyName];
      hadCachedValue = true;
    }

    // Check if the CP has been wrapped
    if (func.wrappedFunction) { func = func.wrappedFunction; }

    // For backwards-compatibility with computed properties
    // that check for arguments.length === 2 to determine if
    // they are being get or set, only pass the old cached
    // value if the computed property opts into a third
    // argument.
    if (func.length === 3) {
      ret = func.call(obj, keyName, value, cachedValue);
    } else if (func.length === 2) {
      ret = func.call(obj, keyName, value);
    } else {
      Ember.defineProperty(obj, keyName, null, cachedValue);
      Ember.set(obj, keyName, value);
      return;
    }

    if (hadCachedValue && cachedValue === ret) { return; }

    if (watched) { Ember.propertyWillChange(obj, keyName); }

    if (hadCachedValue) {
      delete cache[keyName];
    }

    if (cacheable) {
      if (!watched && !hadCachedValue) {
        addDependentKeys(this, obj, keyName, meta);
      }
      cache[keyName] = ret;
    }

    if (watched) { Ember.propertyDidChange(obj, keyName); }
  } finally {
    this._suspended = oldSuspended;
  }
  return ret;
};

/* called when property is defined */
ComputedPropertyPrototype.setup = function(obj, keyName) {
  var meta = obj[META_KEY];
  if (meta && meta.watching[keyName]) {
    addDependentKeys(this, obj, keyName, metaFor(obj));
  }
};

/* called before property is overridden */
ComputedPropertyPrototype.teardown = function(obj, keyName) {
  var meta = metaFor(obj);

  if (meta.watching[keyName] || keyName in meta.cache) {
    removeDependentKeys(this, obj, keyName, meta);
  }

  if (this._cacheable) { delete meta.cache[keyName]; }

  return null; // no value to restore
};

/**
  This helper returns a new property descriptor that wraps the passed
  computed property function. You can use this helper to define properties
  with mixins or via `Ember.defineProperty()`.

  The function you pass will be used to both get and set property values.
  The function should accept two parameters, key and value. If value is not
  undefined you should set the value first. In either case return the
  current value of the property.

  @method computed
  @for Ember
  @param {Function} func The computed property function.
  @return {Ember.ComputedProperty} property descriptor instance
*/
Ember.computed = function(func) {
  var args;

  if (arguments.length > 1) {
    args = a_slice.call(arguments, 0, -1);
    func = a_slice.call(arguments, -1)[0];
  }

  var cp = new ComputedProperty(func);

  if (args) {
    cp.property.apply(cp, args);
  }

  return cp;
};

/**
  Returns the cached value for a property, if one exists.
  This can be useful for peeking at the value of a computed
  property that is generated lazily, without accidentally causing
  it to be created.

  @method cacheFor
  @for Ember
  @param {Object} obj the object whose property you want to check
  @param {String} key the name of the property whose cached value you want
    to return
*/
Ember.cacheFor = function cacheFor(obj, key) {
  var cache = metaFor(obj, false).cache;

  if (cache && key in cache) {
    return cache[key];
  }
};

/**
  @method computed.not
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.not = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    return !get(this, dependentKey);
  });
};

/**
  @method computed.none
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.none = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    var val = get(this, dependentKey);
    return Ember.isNone(val);
  });
};

/**
  @method computed.empty
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.empty = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    var val = get(this, dependentKey);
    return Ember.isEmpty(val);
  });
};

/**
  @method computed.bool
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.bool = function(dependentKey) {
  return Ember.computed(dependentKey, function(key) {
    return !!get(this, dependentKey);
  });
};

/**
  @method computed.alias
  @for Ember
  @param {String} dependentKey
*/
Ember.computed.alias = function(dependentKey) {
  return Ember.computed(dependentKey, function(key, value){
    if (arguments.length === 1) {
      return get(this, dependentKey);
    } else {
      set(this, dependentKey, value);
      return value;
    }
  });
};
<|endoftext|>


<commit_before>//https://stackoverflow.com/a/20548330/8480874

var g_SongPlaying = false;
var g_PauseEvent = new CustomEvent("Pause Song", { "detail": "Event to let any playing song know it needs to pause" });

function Sample(obj, title, desc, src_audio, src_cover) {

    // safety check for myself...
    if( ! $(obj).hasClass('audio_box') ) {
        alert('dumn ass u need to pass a .audio_box!!');
    }
    this.root = $(obj)[0];
    this.title = title;
    this.desc = desc;
    this.id = hash(src_audio);

    generateHTML(this, src_audio, src_cover);

    this.audio = $('#audio' + this.id);
    this.play_btn = $('#play_btn' + this.id);
    this.pause_btn = $('#pause_btn' + this.id);
    this.volume_btn = $('#volume_btn' + this.id);
    this.range = $('#range' + this.id);
    this.slider = $('#slider' + this.id);
    this.prog = $('#prog' + this.id);
    this.prog.tooltip_shown = false;
    this.prog_bar = $('#prog_bar' + this.id);
    this.time = $('#timestamp' + this.id);

    /* init members */
    this.audio[0].volume = 0.8;
    this.pause_btn.hide();
    this.range.hide();

    /* Member Functions */
    this.play = function() {
        document.dispatchEvent(g_PauseEvent);
        while(g_SongPlaying) { }
        $(this).toggle();
        $('#' + this.id.replace('play', 'pause')).toggle();
        g_SongPlaying = true;
        $('#' + this.id.replace('play_btn', 'audio')).trigger('play');
    }

    this.pause = function() {
        $('#' + this.id.replace('pause_btn', 'audio')).trigger('pause');
        g_SongPlaying = false;
        $(this).toggle();
        $('#' + this.id.replace('pause', 'play')).toggle();
    }

    this.stop = function() {
        this.audio.trigger('pause');
        this.audio.currentTime = 0;
        g_SongPlaying = false;
        this.play_btn.show();
        this.pause_btn.hide();
    }

    this.setTooltip = function(e) {
        var offset = this.offsetWidth;
        if( ! this.tooltip_shown ) {
            this.tooltip_shown = true;
            $(this).tooltip('show');
            $('.tooltip').css('margin-top', '-5px');
        }
        $('.tooltip').css('left', e.offsetX.toString() + 'px');
        $('.tooltip-inner').text( track(e,offset, this.id.replace('prog', '')) );
    }

    this.removeTooltip = function() {
        $(this).tooltip('hide');
        this.tooltip_shown = false;
    }

    this.seek = function(e) {
        //console.log("pb1 cliked!",e);
        var percent = e.offsetX / this.prog.offsetWidth;
        //console.log(percent);
        this.audio[0].currentTime = Math.floor(percent * this.audio[0].duration);
    }

    this.update = function() {
        //console.log('timeupdate');
        this.prog_bar.css('width', ((this.audio[0].currentTime / this.audio[0].duration)*100) + '%').attr('aria-valuenow', (this.audio[0].currentTime / this.audio[0].duration)*100);

        var rem = parseInt(this.audio[0].duration - this.audio[0].currentTime, 10),
        mins = Math.floor(rem/60,10),
        secs = rem - mins*60;

        this.time.text('-' + mins + ':' + (secs > 9 ? secs : '0' + secs));
    }

    this.showVolumeButton = function(id) {
        $('#range' + id).hide();
        $('#volume_btn' + id).show();
    }

    this.showVolumeSlider = function() {
        $('#volume_btn' + this.id).hide();
        $('#range' + this.id).show();
    }

    /* event listeners */
    this.play_btn.click(this.play);
    this.pause_btn.click(this.pause);
    this.root.addEventListener(g_PauseEvent, this.pause);
    this.volume_btn.hover(this.showVolumeSlider(this.id));
    this.range.mouseleave(this.showVolumeButton(this.id));
    this.audio[0].addEventListener('ended', this.stop);
    this.audio[0].addEventListener('ontimeupdate', this.update);
    this.prog.click(this.seek);
    this.prog.mousemove(this.setTooltip);
    this.prog.mouseleave(this.removeTooltip);

    // utility functions
    function hash(str) {
        var hash = 0, i, chr;
        if (str.length === 0) return hash;
        for (i = 0; i < str.length; i++) {
            chr   = str.charCodeAt(i);
            hash  = ((hash << 5) - hash) + chr;
            hash |= 0; // Convert to 32bit integer
        }
        return hash;
    }

    function generateHTML(obj, src_audio, src_cover) {
        obj.root.innerHTML += '

';
        obj.root.innerHTML += '

' + obj.title + ' by N.Gattuso

' + obj.desc + '

<input id="slider' + obj.id + '" class="range-slider__range" type="range" value="80" min="0" max="100" step="5" for="#s2">0

0:00

<audio id="audio' + obj.id + '" src="' + src_audio + '" preload="metadata"></audio>

';
    }

    function track(e, offset, id) {
        var percent = e.offsetX / offset;
        var audio = $('#audio' + id);
        var time = percent * audio[0].duration;
        mins = Math.floor(time/60,10),
        secs = Math.floor(time - mins*60);
        var text = '' + mins.toString() + ':';
        text += secs > 9 ? secs.toString() : '0' + secs.toString();
        //console.log(text);

        return text;
    }
}

$(document).ready(function() {
    $('[data-toggle="tooltip"]').tooltip({
        delay: {
            show: 500,
            hide: 100
        }
    });
});
<commit_msg>progress bar is now updated as song plays<commit_after>//https://stackoverflow.com/a/20548330/8480874

var g_SongPlaying = false;
var g_PauseEvent = new CustomEvent("Pause Song", { "detail": "Event to let any playing song know it needs to pause" });

function Sample(obj, title, desc, src_audio, src_cover) {

    // safety check for myself...
    if( ! $(obj).hasClass('audio_box') ) {
        alert('dumn ass u need to pass a .audio_box!!');
    }
    this.root = $(obj)[0];
    this.title = title;
    this.desc = desc;
    this.id = hash(src_audio);

    generateHTML(this, src_audio, src_cover);

    this.audio = $('#audio' + this.id);
    this.play_btn = $('#play_btn' + this.id);
    this.pause_btn = $('#pause_btn' + this.id);
    this.volume_btn = $('#volume_btn' + this.id);
    this.range = $('#range' + this.id);
    this.slider = $('#slider' + this.id);
    this.prog = $('#prog' + this.id);
    this.prog.tooltip_shown = false;
    this.prog_bar = $('#prog_bar' + this.id);
    this.time = $('#timestamp' + this.id);

    /* init members */
    this.audio[0].volume = 0.8;
    this.pause_btn.hide();
    this.range.hide();

    /* Member Functions */
    this.play = function() {
        document.dispatchEvent(g_PauseEvent);
        while(g_SongPlaying) { }
        $(this).toggle();
        $('#' + this.id.replace('play', 'pause')).toggle();
        g_SongPlaying = true;
        $('#' + this.id.replace('play_btn', 'audio')).trigger('play');
    }

    this.pause = function() {
        $('#' + this.id.replace('pause_btn', 'audio')).trigger('pause');
        g_SongPlaying = false;
        $(this).toggle();
        $('#' + this.id.replace('pause', 'play')).toggle();
    }

    this.stop = function() {
        this.audio.trigger('pause');
        this.audio.currentTime = 0;
        g_SongPlaying = false;
        this.play_btn.show();
        this.pause_btn.hide();
    }

    this.setTooltip = function(e) {
        var offset = this.offsetWidth;
        if( ! this.tooltip_shown ) {
            this.tooltip_shown = true;
            $(this).tooltip('show');
            $('.tooltip').css('margin-top', '-5px');
        }
        $('.tooltip').css('left', e.offsetX.toString() + 'px');
        $('.tooltip-inner').text( track(e,offset, this.id.replace('prog', '')) );
    }

    this.removeTooltip = function() {
        $(this).tooltip('hide');
        this.tooltip_shown = false;
    }

    this.seek = function(e) {
        //console.log("pb1 cliked!",e);
        var percent = e.offsetX / this.prog.offsetWidth;
        //console.log(percent);
        this.audio[0].currentTime = Math.floor(percent * this.audio[0].duration);
    }

    this.showVolumeButton = function(id) {
        $('#range' + id).hide();
        $('#volume_btn' + id).show();
    }

    this.showVolumeSlider = function() {
        $('#volume_btn' + this.id).hide();
        $('#range' + this.id).show();
    }

    /* event listeners */
    this.play_btn.click(this.play);
    this.pause_btn.click(this.pause);
    this.root.addEventListener(g_PauseEvent, this.pause);
    this.volume_btn.hover(this.showVolumeSlider(this.id));
    this.range.mouseleave(this.showVolumeButton(this.id));
    this.audio[0].addEventListener('ended', this.stop);
    this.audio[0].addEventListener('ontimeupdate', this.update);
    this.prog.click(this.seek);
    this.prog.mousemove(this.setTooltip);
    this.prog.mouseleave(this.removeTooltip);

    // utility functions
    function hash(str) {
        var hash = 0, i, chr;
        if (str.length === 0) return hash;
        for (i = 0; i < str.length; i++) {
            chr   = str.charCodeAt(i);
            hash  = ((hash << 5) - hash) + chr;
            hash |= 0; // Convert to 32bit integer
        }
        return hash;
    }

    function generateHTML(obj, src_audio, src_cover) {
        obj.root.innerHTML += '

';
        obj.root.innerHTML += '

' + obj.title + ' by N.Gattuso

' + obj.desc + '

<input id="slider' + obj.id + '" class="range-slider__range" type="range" value="80" min="0" max="100" step="5" for="#s2">0

0:00

<audio id="audio' + obj.id + '" src="' + src_audio + '" preload="metadata"  ontimeupdate="update(' + obj.id + ');"></audio>

';
    }

    function track(e, offset, id) {
        var percent = e.offsetX / offset;
        var audio = $('#audio' + id);
        var time = percent * audio[0].duration;
        mins = Math.floor(time/60,10),
        secs = Math.floor(time - mins*60);
        var text = '' + mins.toString() + ':';
        text += secs > 9 ? secs.toString() : '0' + secs.toString();
        //console.log(text);

        return text;
    }
}

function update(id) {
    //console.log('timeupdate');
    $('#prog_bar' + id.toString()).css('width', (($('#audio' + id.toString())[0].currentTime / $('#audio' + id.toString())[0].duration)*100) + '%').attr('aria-valuenow', ($('#audio' + id.toString())[0].currentTime / $('#audio' + id.toString())[0].duration)*100);

    var rem = parseInt($('#audio' + id.toString())[0].duration - $('#audio' + id.toString())[0].currentTime, 10),
    mins = Math.floor(rem/60,10),
    secs = rem - mins*60;

    $('#timestamp' + id.toString()).text('-' + mins + ':' + (secs > 9 ? secs : '0' + secs));
}

$(document).ready(function() {
    $('[data-toggle="tooltip"]').tooltip({
        delay: {
            show: 500,
            hide: 100
        }
    });
});
<|endoftext|>


<commit_before>var serialPort = require('serialport');
var onExit = require('signal-exit');

var START_VAL        = 0x7E;
var MSG_TYPE_DMX_OUT = 0x06;
var DMX_STARTCODE    = 0x00;
var END_VAL          = 0xE7;

var fps = 40;
var dmxPro;
var open = false;

var portOpen = function(err) {
  console.log('OPEN');
  open = true;
  setInterval(dmxLoop, 1000/fps);
}

var portError = function(err) {
  console.log('DMX Device error',err);
};

var portClose = function(err) {
  open = false;
  console.log('DMX Device close',err);
};

var initPort = function(port) {
  dmxPro = new serialPort.SerialPort(port.comName, {
    baudrate: 57600,
    stopBits: 2
  });
  dmxPro.on('open',  portOpen);
  dmxPro.on('error', portError);
  dmxPro.on('close', portClose);
};

onExit(function() {
  if(open) {
    console.log('AUTO CLOSING');
    dmxPro.close();
  }
});

var findAndConnect = function() {
  serialPort.list(function (err, ports) {
    ports.forEach(function(port) {
      console.log(port);
      if(port.manufacturer == 'DMXking.com') {
        initPort(port);
      }
    });
  });
};

var sendBuffer;
var channels = 0;

var prepareBuffer = function() {
  sendBuffer = new Buffer(channels+6);
  sendBuffer.fill(0x00);
  sendBuffer[0] = START_VAL;
  sendBuffer[1] = MSG_TYPE_DMX_OUT;
  sendBuffer[2] = (channels+1) & 0xFF;
  sendBuffer[3] = (channels+1) >> 8;
  sendBuffer[4] = DMX_STARTCODE;
  sendBuffer[5 + channels] = END_VAL;
}

var setChannels = function(values) {
  if(values.length > 512) {
    console.log('Too many channels');
    return;
  }
  if(values.length != channels) {
    channels = values.length;
    prepareBuffer();
  }
  inBuffer.copy(sendBuffer,5);
  /*for(var i=0; i<values.length; i++) {
    sendBuffer[i+5] = values[i];
  }*/
}

var dmxLoop = function() {
  dmxPro.write(sendBuffer, onSend);
};

var onSend = function(error) {
  if(error) {
    console.log('Error on SEND',error);
  }
};

findAndConnect();

module.exports = {
  setChannels: setChannels
};
<commit_msg>Fixing variable name<commit_after>var serialPort = require('serialport');
var onExit = require('signal-exit');

var START_VAL        = 0x7E;
var MSG_TYPE_DMX_OUT = 0x06;
var DMX_STARTCODE    = 0x00;
var END_VAL          = 0xE7;

var fps = 40;
var dmxPro;
var open = false;

var portOpen = function(err) {
  console.log('OPEN');
  open = true;
  setInterval(dmxLoop, 1000/fps);
}

var portError = function(err) {
  console.log('DMX Device error',err);
};

var portClose = function(err) {
  open = false;
  console.log('DMX Device close',err);
};

var initPort = function(port) {
  dmxPro = new serialPort.SerialPort(port.comName, {
    baudrate: 57600,
    stopBits: 2
  });
  dmxPro.on('open',  portOpen);
  dmxPro.on('error', portError);
  dmxPro.on('close', portClose);
};

onExit(function() {
  if(open) {
    console.log('AUTO CLOSING');
    dmxPro.close();
  }
});

var findAndConnect = function() {
  serialPort.list(function (err, ports) {
    ports.forEach(function(port) {
      console.log(port);
      if(port.manufacturer == 'DMXking.com') {
        initPort(port);
      }
    });
  });
};

var sendBuffer;
var channels = 0;

var prepareBuffer = function() {
  sendBuffer = new Buffer(channels+6);
  sendBuffer.fill(0x00);
  sendBuffer[0] = START_VAL;
  sendBuffer[1] = MSG_TYPE_DMX_OUT;
  sendBuffer[2] = (channels+1) & 0xFF;
  sendBuffer[3] = (channels+1) >> 8;
  sendBuffer[4] = DMX_STARTCODE;
  sendBuffer[5 + channels] = END_VAL;
}

var setChannels = function(inBuffer) {
  if(inBuffer.length > 512) {
    console.log('Too many channels');
    return;
  }
  if(inBuffer.length != channels) {
    channels = inBuffer.length;
    prepareBuffer();
  }
  inBuffer.copy(sendBuffer,5);
  /*for(var i=0; i<values.length; i++) {
    sendBuffer[i+5] = values[i];
  }*/
}

var dmxLoop = function() {
  dmxPro.write(sendBuffer, onSend);
};

var onSend = function(error) {
  if(error) {
    console.log('Error on SEND',error);
  }
};

findAndConnect();

module.exports = {
  setChannels: setChannels
};
<|endoftext|>


<commit_before>/* From Homey SDK 2.0 docs: The file device.js is a representation of an already paired device on Homey */

"use strict";

const Homey = require("homey");

// We need network functions.
var net = require("net");

// keep a list of devices in memory
var devices = [];

// MVMax is the maximum volume number (e.g. 70)
var MVMax = 70;

// The Denon/Marantz IP network interface always uses port 23, which is known as the telnet port.
var telnetPort = 23;

// All known inputs for supported Denon/Marantz AV receivers and a more friendly name to use.
// If you find your favorite input missing, please file a bug on the GitHub repository.
var allPossibleInputs = [{
        inputName: "PHONO",
        friendlyName: "Phono"
    },
    {
        inputName: "CD",
        friendlyName: "CD player"
    },
    {
        inputName: "DVD",
        friendlyName: "DVD player"
    },
    {
        inputName: "BD",
        friendlyName: "BluRay player"
    },
    {
        inputName: "TV",
        friendlyName: "TV"
    },
    {
        inputName: "SAT/CBL",
        friendlyName: "SAT/Cable TV"
    },
    {
        inputName: "SAT",
        friendlyName: "Satellite"
    },
    {
        inputName: "MPLAY",
        friendlyName: "Media Player"
    },
    {
        inputName: "VCR",
        friendlyName: "VCR"
    },
    {
        inputName: "GAME",
        friendlyName: "Game"
    },
    {
        inputName: "V.AUX",
        friendlyName: "Video Aux"
    },
    {
        inputName: "TUNER",
        friendlyName: "Tuner"
    },
    {
        inputName: "DOCK",
        friendlyName: "Dock"
    },
    {
        inputName: "HDRADIO",
        friendlyName: "HD Radio"
    },
    {
        inputName: "SIRIUS",
        friendlyName: "Sirius Radio"
    },
    {
        inputName: "SPOTIFY",
        friendlyName: "Spotify"
    },
    {
        inputName: "SIRIUSXM",
        friendlyName: "SiriusXM"
    },
    {
        inputName: "RHAPSODY",
        friendlyName: "Rhapsody"
    },
    {
        inputName: "PANDORA",
        friendlyName: "Pandora"
    },
    {
        inputName: "NAPSTER",
        friendlyName: "Napster"
    },
    {
        inputName: "LASTFM",
        friendlyName: "Last.fm"
    },
    {
        inputName: "FLICKR",
        friendlyName: "Flickr"
    },
    {
        inputName: "IRADIO",
        friendlyName: "Internet Radio"
    },
    {
        inputName: "SERVER",
        friendlyName: "Server"
    },
    {
        inputName: "FAVORITES",
        friendlyName: "Favorites"
    },
    {
        inputName: "CDR",
        friendlyName: "CDR"
    },
    {
        inputName: "AUX1",
        friendlyName: "Aux 1"
    },
    {
        inputName: "AUX2",
        friendlyName: "Aux 2"
    },
    {
        inputName: "AUX3",
        friendlyName: "Aux 3"
    },
    {
        inputName: "AUX4",
        friendlyName: "Aux 4"
    },
    {
        inputName: "AUX5",
        friendlyName: "Aux 5"
    },
    {
        inputName: "AUX6",
        friendlyName: "Aux 6"
    },
    {
        inputName: "AUX7",
        friendlyName: "Aux 7"
    },
    {
        inputName: "NET",
        friendlyName: "Net"
    },
    {
        inputName: "NET/USB",
        friendlyName: "Net/USB"
    },
    {
        inputName: "BT",
        friendlyName: "Bluetooth"
    },
    {
        inputName: "M-XPORT",
        friendlyName: "M-XPort"
    },
    {
        inputName: "USB/IPOD",
        friendlyName: "USB/iPod"
    },
    {
        inputName: "USB",
        friendlyName: "USB port"
    },
    {
        inputName: "IPD",
        friendlyName: "iPod direct start playback"
    },
    {
        inputName: "IRP",
        friendlyName: "Internet Radio Recent Play"
    },
    {
        inputName: "FVP",
        friendlyName: "Online Music Favorites Play"
    },
    {
        inputName: "OTP",
        friendlyName: "One Touch Play"
    },
    {
        inputName: "IPOD",
        friendlyName: "iPod"
    },
    {
        inputName: "AUXA",
        friendlyName: "Aux A"
    },
    {
        inputName: "AUXB",
        friendlyName: "Aux B"
    },
    {
        inputName: "AUXC",
        friendlyName: "Aux C"
    }
];

var triggerInputChangedAction = null;
var triggerInputSourceAction = null;

class DMDevice extends Homey.Device {

    // this method is called when the Device is inited
    onInit() {
            this.log("device init");
            this.log("name: ", this.getName());
            this.log("class: ", this.getClass());
            let id = this.getData().id;
            this.log("id: ", id);

            devices[id] = {};
            devices[id].client = "";
            devices[id].receivedData = "";
            devices[id].MVMax = 70;

            // get initial state
            this.getState();

            // register capability listeners
            this.registerCapabilityListener('onoff', this.onCapabilityOnoff.bind(this));
            this.registerCapabilityListener('volume_set', this.onCapabilityVolumeSet.bind(this));
            this.registerCapabilityListener('volume_mute', this.onCapabilityVolumeMute.bind(this));
            this.registerCapabilityListener('volume_up', this.onCapabilityVolumeUp.bind(this));
            this.registerCapabilityListener('volume_down', this.onCapabilityVolumeDown.bind(this));
            this.registerCapabilityListener('inputsource', this.onCapabilityChangeInputSource.bind(this), (value, opts) => {
                this.log('value', value);
                this.log('opts', opts);
                return Promise.resolve();
            });

            // register flow card actions
            let powerOnAction = new Homey.FlowCardAction('powerOn');
            powerOnAction
                .register().registerRunListener((args, state) => {
                    this.log("Flow card action powerOn args.zone: " + JSON.stringify(args.zone));
                    this.powerOn(args.device, args.zone.zone);
                    return Promise.resolve(true);
                });
            powerOnAction
                .getArgument('zone').registerAutocompleteListener((query, args) => {
                    var items = this.availableZones(args.device, query);
                    return Promise.resolve(items);
                });

            let powerOffAction = new Homey.FlowCardAction('powerOff');
            powerOffAction
                .register().registerRunListener((args, state) => {
                    this.log("Flow card action powerOff args " + JSON.stringify(args));
                    this.powerOff(args.device, args.zone.zone);
                    return Promise.resolve(true);
                });
            powerOffAction
                .getArgument('zone').registerAutocompleteListener((query, args) => {
                    var items = this.availableZones(args.device, query);
                    return Promise.resolve(items);
                });

            let muteAction = new Homey.FlowCardAction('mute');
            muteAction
                .register().registerRunListener((args, state) => {
                    this.log("Flow card action mute args " + JSON.stringify(args));
                    this.onActionMute(args.device, args.zone.zone);
                    return Promise.resolve(true);
                });
            muteAction
                .getArgument('zone').registerAutocompleteListener((query, args) => {
                    var items = this.availableZones(args.device, query);
                    return Promise.resolve(items);
                });

            let unMuteAction = new Homey.FlowCardAction('unMute');
            unMuteAction
                .register().registerRunListener((args, state) => {
                    this.log("Flow card action unMute args " + JSON.stringify(args));
                    this.onActionUnMute(args.device, args.zone.zone);
                    return Promise.resolve(true);
                });
            unMuteAction
                .getArgument('zone').registerAutocompleteListener((query, args) => {
                    var items = this.availableZones(args.device, query);
                    return Promise.resolve(items);
                });

            let setVolumeAction = new Homey.FlowCardAction('setVolume');
            setVolumeAction
                .register().registerRunListener((args, state) => {
                    this.log("Flow card action setVolume args " + JSON.stringify(args));
                    this.log(" setVolume volume " + args.volume);
                    this.onActionSetVolume(args.device, args.zone.zone, args.volume);
                    return Promise.resolve(true);
                });
            setVolumeAction
                .getArgument('zone').registerAutocompleteListener((query, args) => {
                    var items = this.availableZones(args.device, query);
                    return Promise.resolve(items);
                });

            let setVolumeStepAction = new Homey.FlowCardAction('setVolumeStep');
            setVolumeStepAction
                .register().registerRunListener((args, state) => {
                    this.log("Flow card action setVolumeStep args: " + JSON.stringify(args));
                    this.log(" setVolumeStep volumeChange " + args.volumeChange);
                    this.onActionSetVolumeStep(args.device, args.zone.zone, args.volumeChange);
                    return Promise.resolve(true);
                });
            setVolumeStepAction
                .getArgument('zone').registerAutocompleteListener((query, args) => {
                    var items = this.availableZones(args.device, query);
                    return Promise.resolve(items);
                });

            let changeInputAction = new Homey.FlowCardAction('changeInput');
            changeInputAction
                .register()
                .registerRunListener((args, state) => {
                    this.log("Flow card action changeInput args input " + JSON.stringify(args));
                    this.onActionChangeInput(args.device, args.zone.zone, args.input.inputName);
                    return Promise.resolve(true);
                });
            changeInputAction
                .getArgument('zone')
                .registerAutocompleteListener((query, args) => {
                    var items = this.availableZones(args.device, query);
                    return Promise.resolve(items);
                });
            changeInputAction
                .getArgument('input')
                .registerAutocompleteListener((query, args) => {
                    var items = this.searchForInputsByValue(query);
                    return Promise.resolve(items);
                });

            new Homey.FlowCardAction('customCommand').register().registerRunListener((args, state) => {
                this.log("Flow card action customCommand args " + JSON.stringify(args));
                this.log(" customCommand command " + args.command);
                this.onActionCustomCommand(args.device, args.command);
                return Promise.resolve(true);
            });

            // register flow card triggers
            triggerInputSourceAction = new Homey.FlowCardTriggerDevice('inputsource');
            triggerInputSourceAction.register().registerRunListener((args, state) => {
                args.device.log("Flow card trigger input args: " + JSON.stringify(args));
                args.device.log("Flow card trigger input state: " + JSON.stringify(state));
                //this.powerOn(args.device, args.zone.zone);
                if (state.id == args.device.getData().id) {
                    return Promise.resolve(state.input == args.input.name);
                } else {
                    return Promise.resolve(false);
                }
            });

            triggerInputSourceAction.getArgument('input').registerAutocompleteListener((query, args) => {
                var items = this.searchForInputsByValue(query);
                return Promise.resolve(items);
            });

            triggerInputChangedAction = new Homey.FlowCardTriggerDevice('inputchanged');
            triggerInputChangedAction.register().registerRunListener((args, state) => {
                args.device.log("Flow card trigger inputchanged args: " + JSON.stringify(args));
                args.device.log("Flow card trigger inputchanged state: " + JSON.stringify(state));
                //this.powerOn(args.device, args.zone.zone);
                return Promise.resolve(state.id == args.device.getData().id);
            });

            // register flow card conditions
            let inputCondition = new Homey.FlowCardCondition('is_input');
            inputCondition
                .register()
                .registerRunListener((args, state) => {
                    args.device.log("Flow card condition input inputsource capability value: " + args.device.getCapabilityValue("inputsource"));
                    args.device.log("Flow card condition input args.input.name: " + JSON.stringify(args.input.name));
                    let inputsource = args.device.getCapabilityValue("inputsource") == args.input.name;
                    return Promise.resolve(inputsource);
                });
            inputCondition
                .getArgument('input')
                .registerAutocompleteListener((query, args) => {
                    var items = this.searchForInputsByValue(query);
                    return Promise.resolve(items);
                });

        } // end onInit

    // this method is called when the Device is added
    onAdded() {
        let id = this.getData().id;
        this.log("device added: ", id);
        devices[id] = {};
        devices[id].client = "";
        devices[id].receivedData = "";
        devices[id].MVMax = 70;
    }

    // this method is called when the Device is deleted
    onDeleted() {
        let id = this.getData().id;
        this.log("device deleted: ", id);
        delete devices[id];
    }

    // this method is called when the Device has requested a state change (turned on or off)
    onCapabilityOnoff(value, opts, callback) {
        // ... set value to real device
        this.log("Capability called: onoff value: ", value);
        if (value) {
            this.powerOn(this, "Whole unit");
        } else {
            this.powerOff(this, "Whole unit");
        }
        // Then, emit a callback ( err, result )
        callback(null);
    }

    onCapabilityVolumeMute(value, opts, callback) {
        this.log("Capability called: volume_mute value: ", value);
        if (value) {
            this.mute(this, "Main Zone");
        } else {
            this.muteOff(this, "Main Zone");
        }
        callback(null);
    }

    onCapabilityVolumeSet(value, opts, callback) {
        var targetVolume = Math.round(value * MVMax);
        this.log("Capability called: volume_set, value: " + value + " calculated volume: " + targetVolume);
        this.setVolume(this, "Main Zone", targetVolume);
        callback(null);
    }

    onCapabilityVolumeUp(value, opts, callback) {
        this.log("Capability called: volume_up");
        this.volumeUp(this, "Main Zone");
        callback(null);
    }

    onCapabilityVolumeDown(value, opts, callback) {
        this.log("Capability called: volume_down");
        this.volumeDown(this, "Main Zone");
        callback(null);
    }

    onCapabilityChangeInputSource(value, opts, callback) {
        // ... set value to real device
        this.log("Capability called: inputsource value: ", value);
        this.changeInputSource(this, "Main Zone", value);
        // Then, emit a callback ( err, result )
        callback(null);
    }

    onActionMute(device, zone) {
        device.log("Action called: mute");
        device.mute(device, zone);
    }

    onActionUnMute(device, zone) {
        device.log("Action called: unMute");
        device.unMute(device, zone);
    }

    onActionSetVolume(device, zone, volume) {
        device.log("Action called: setVolume");
        device.setVolume(device, zone, volume);
    }

    onActionSetVolumeStep(device, zone, volumeChange) {
        device.log("Action called: setVolumeStep");
        device.setVolumeStep(device, zone, volumeChange);
    }

    onActionChangeInput(device, zone, input) {
        device.log("Action called: changeInput");
        device.changeInputSource(device, zone, input);
    }

    // Note: customCommand affects all zones for a device, so you can run a customCommand from Zone2 and it will run just as if it was run from mainZone
    onActionCustomCommand(device, command) {
        device.log("Action called: customCommand");
        command += "\r";
        device.sendCommand(device, command);
    }

    getState() {
        this.log("Getting device status");
        // whole unit
        this.sendCommand(this, "PW?\r");
        // main zone
        this.sendCommand(this, "ZM?\r"); // Zone Main On?
        this.sendCommand(this, "MV?\r"); // Main Volume?
        // zone 2 if applicable
        if (this.getSettings().settingZone2) {
            this.sendCommand(this, "Z2?\r");
        }
        // zone 3 if applicable
        if (this.getSettings().settingZone3) {
            this.sendCommand(this, "Z3?\r");
        }
    }

    parseResponse(device) {
        let id = device.getData().id;
        let receivedData = devices[id].receivedData;

        if (receivedData.indexOf("PWON") >= 0) {
            device.setCapabilityValue("onoff", true);
            device.log("parseResponse: set onoff true");
        }

        if (receivedData.indexOf("PWSTANDBY") >= 0) {
            device.setCapabilityValue("onoff", false);
            device.log("parseResponse: set onoff false");
        }

        if (receivedData.indexOf("SI") >= 0) {
            for (var i = 0; i < allPossibleInputs.length; i++) {
                if (receivedData.indexOf("SI" + allPossibleInputs[i].inputName) >= 0) {
                    device.log("parseResponse: set to source " + allPossibleInputs[i].friendlyName);
                    if (device.hasCapability("inputsource")) {
                        device.setCapabilityValue("inputsource", allPossibleInputs[i].friendlyName, function(err, result) {
                            if (err != null) {
                                device.log(' setCapabilityValue: inputsource: ' + err);
                            }
                            if (result != undefined) {
                                device.log(' setCapabilityValue: inputsource: ' + result);
                            }
                        });
                    }

                    // trigger the card
                    let tokens = { 'input': allPossibleInputs[i].friendlyName };
                    let state = { 'id': id, 'input': allPossibleInputs[i].friendlyName };
                    triggerInputSourceAction.trigger(device, tokens, state)
                        .then(this.log)
                        .catch(this.error)
                    triggerInputChangedAction.trigger(device, tokens, state)
                        .then(this.log)
                        .catch(this.error)
                    break;
                }
            }
        }

        if (receivedData.indexOf("MVMAX") >= 0) {
            var max = receivedData.lastIndexOf("MVMAX");
            var maxSlice = receivedData.slice(max);
            var maxRes = maxSlice.split(";");
            MVMax = maxRes[0].substr(6, 2); // ignore possible third digit
            device.log("parseResponse: found MVMAX of " + MVMax);
            let id = device.getData().id;
            devices[id].MVMax = MVMax;
        }

        // Run a Regular Expression to find the first Main Volume response (if any)
        var MVRegEx = /MV(\d){2}/;
        var MainVolumeFound = MVRegEx.exec(receivedData);

        if (MainVolumeFound) {
            var MainVolumeNumber = MainVolumeFound[0].substr(2, 2);
            var MainVolume = MainVolumeNumber / MVMax;
            device.setCapabilityValue("volume_set", MainVolume);
            device.log("parseResponse: set setVolume " + MainVolume + " = " + Math.floor((MainVolume * 100).toFixed(3)) + '%');
        }
        // done with the receivedData, clear it for the next responses
        devices[id].receivedData = "";
    }

    powerOn(device, zone) {
        // supported zones: "Whole unit" (default), "Main Zone", "Zone2"
        var command = "PWON\r";
        switch (zone) {
            case "Whole unit":
                command = "PWON\r";
                break;
            case "Main Zone":
                command = "ZMON\r";
                break;
            case "Zone2":
                command = "Z2ON\r";
                break;
        }
        device.sendCommand(device, command);
    }

    powerOff(device, zone) {
        // supported zones: "Whole unit" (default), "Main Zone", "Zone2"
        var command = "PWSTANDBY\r";
        switch (zone) {
            case "Whole unit":
                command = "PWSTANDBY\r";
                break;
            case "Main Zone":
                command = "ZMOFF\r";
                break;
            case "Zone2":
                command = "Z2OFF\r";
                break;
        }
        device.sendCommand(device, command);
    }

    mute(device, zone) {
        // supported zones: "Main Zone" (default), "Zone2", "Zone3"
        var command = "MUON\r";
        switch (zone) {
            case "Main Zone":
                command = "MUON\r";
                break;
            case "Zone2":
                command = "Z2MUON\r";
                break;
            case "Zone3":
                command = "Z3MUON\r";
                break;
        }
        device.sendCommand(device, command);
    }

    unMute(device, zone) {
        // supported zones: "Main Zone" (default), "Zone2", "Zone3"
        var command = "MUOFF\r";
        switch (zone) {
            case "Main Zone":
                command = "MUOFF\r";
                break;
            case "Zone2":
                command = "Z2MUOFF\r";
                break;
            case "Zone3":
                command = "Z3MUOFF\r";
                break;
        }
        device.sendCommand(device, command);
    }

    setVolume(device, zone, targetVolume) {
        // volume ranges from 00 to 99
        // half steps are possible but not used here, eg 805 is 80.5
        // according to Marantz protocol some models have 99 as --, some have 00 as --
        var asciiVolume = "0" + targetVolume.toString();
        asciiVolume = asciiVolume.slice(-2);
        // supported zones: "Main Zone" (default), "Zone2", "Zone3"
        var volumeZone = "MV";
        switch (zone) {
            case "Main Zone":
                volumeZone = "MV";
                break;
            case "Zone2":
                volumeZone = "Z2";
                break;
            case "Zone3":
                volumeZone = "Z3";
                break;
        }
        var command = volumeZone + asciiVolume + "\r";
        device.sendCommand(device, command);
    }

    setVolumeStep(device, zone, volumeChange) {
        // Step up or down the volume. Argument volumeChange is the difference (e.g. +10 is 10 steps up or -5 is 5 steps down)
        var upOrDown = null;
        if (volumeChange > 0) {
            upOrDown = "UP";
        }
        if (volumeChange < 0) {
            upOrDown = "DOWN";
        }
        if (upOrDown !== null) {
            // supported zones: "Main Zone" (default), "Zone2", "Zone3"
            var volumeZone = "MV";
            switch (zone) {
                case "Main Zone":
                    volumeZone = "MV";
                    break;
                case "Zone2":
                    volumeZone = "Z2";
                    break;
                case "Zone3":
                    volumeZone = "Z3";
                    break;
            }
            var command = volumeZone + upOrDown + "\r";
            for (var i = 0; i < Math.abs(volumeChange); i++) {
                setTimeout(device.sendCommand, i * 750, device, command);
            }
        }
    }

    volumeUp(device, zone) {
        var volumeZone = "MV";
        switch (zone) {
            case "Main Zone":
                volumeZone = "MV";
                break;
            case "Zone2":
                volumeZone = "Z2";
                break;
            case "Zone3":
                volumeZone = "Z3";
                break;
        }
        var command = volumeZone + "UP\r";
        device.sendCommand(device, command);
    }

    volumeDown(device, zone) {
        var volumeZone = "MV";
        switch (zone) {
            case "Main Zone":
                volumeZone = "MV";
                break;
            case "Zone2":
                volumeZone = "Z2";
                break;
            case "Zone3":
                volumeZone = "Z3";
                break;
        }
        var command = volumeZone + "DOWN\r";
        device.sendCommand(device, command);
    }

    changeInputSource(device, zone, input) {
        // supported zones: "Main Zone" (default), "Zone2", "Zone3"
        var sourceZone = "SI";
        switch (zone) {
            case "Main Zone":
                sourceZone = "SI";
                break;
            case "Zone2":
                sourceZone = "Z2";
                break;
            case "Zone3":
                sourceZone = "Z3";
                break;
        }
        var command = sourceZone + input + "\r";
        device.sendCommand(device, command);
    }

    //

    sendCommand(device, command) {
        let settings = device.getSettings();
        let hostIP = settings.settingIPAddress;
        let id = device.getData().id;
        let client = devices[id].client;

        // for logging strip last char which will be the newline \n char
        let displayCommand = command.substring(0, command.length - 1);
        device.log("Sending " + displayCommand + " to " + device.getName() + " at " + hostIP);

        // check if client (net.Socket) already exists, if not then open one.
        if ((typeof(client) === 'undefined') || (typeof(client.destroyed) != 'boolean') || (client.destroyed == true)) {
            device.log("Opening new net.Socket to " + hostIP + ":" + telnetPort);
            client = new net.Socket();
            client.connect(telnetPort, hostIP);
            // add handler for any response or other data coming from the device
            client.on('data', function(data) {
                let tempData = data.toString().replace(/\r/g, ";");
                devices[id].receivedData += tempData;
                device.log("Got data: " + tempData + " -- receivedData: " + devices[id].receivedData);
                device.parseResponse(device);
            })
            client.on('error', function(err) {
                device.log("IP socket error: " + err.message);
                if (typeof(client.destroy) == 'function') {
                    client.destroy();
                }
            })
            devices[id].client = client;
        }
        client.write(command);
    }

    searchForInputsByValue(value) {
        // for now, consider all known Marantz/Denon inputs
        var possibleInputs = allPossibleInputs;
        var tempItems = [];
        for (var i = 0; i < possibleInputs.length; i++) {
            var tempInput = possibleInputs[i];
            if (tempInput.friendlyName.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
                tempItems.push({ icon: "", name: tempInput.friendlyName, description: "", inputName: tempInput.inputName });
            }
        }
        return tempItems;
    }

    availableZones(device, value) {
        var possibleZones = [];
        var settings = device.getSettings();
        if (settings.settingZoneMain) possibleZones.push({ icon: "", name: "Main Zone", description: "", zone: "Main Zone" });
        if (settings.settingZone2) possibleZones.push({ icon: "", name: "Zone 2", description: "", zone: "Zone2" });
        if (settings.settingZone3) possibleZones.push({ icon: "", name: "Zone 3", description: "", zone: "Zone3" });
        return possibleZones;
    }
}

module.exports = DMDevice;<commit_msg>wrong location for new device.js<commit_after><|endoftext|>


<commit_before>
    // handles interfacing with GIFEncoder
    var MFAnimatedGIF = function(opts) {
        var encoder;

        var _initialize = function(opts) {

            var canvas = document.createElement("canvas");
            var context = canvas.getContext('2d');

            encoder = new GIFEncoder();
            encoder.setRepeat(opts.repeat);
            encoder.setDelay(opts.delay);
            canvas.width =  opts.width;
            canvas.height = opts.height;
            encoder.setSize(opts.width, opts.height);

            encoder.start();

            for(var i=0; i<opts.images.length; i++) {
                var animframe = opts.images[i];
                var ctx = canvas.getContext('2d');

                ctx.drawImage(animframe, 0, 0, animframe.width, animframe.height, 0, 0, canvas.width, canvas.height);

                encoder.addFrame(ctx);
            }

            encoder.finish();
        };

        var _dataURL = function() {
            return 'data:image/gif;base64,' + $.base64.encode(encoder.stream().getData());
        };

        var _binaryURL = function() {
            // Convert encoder data to binary format
            var data = encoder.stream().getData();
            var byteArray = new Uint8Array(data.length);
            for (var i = 0; i < data.length; i++) {
                byteArray[i] = data.charCodeAt(i) & 0xff;
            }

            var bb = new BlobBuilder();
            bb.append(byteArray.buffer);
            return window.URL.createObjectURL(bb.getBlob("image/gif"));
        };

        _initialize(opts);

        return {
            dataURL: _dataURL,
            binaryURL: _binaryURL
        };
    };

    var App = {};
    App.MAX_BYTES = 2*1024*1024; // 2MB
    App.initialRate = 300; // 300ms delay in frames
    App.timeline = [];

    App.clear = function() {
        finalImage.attr("src", "about:blank").hide();
        fileList.empty();
        App.timeline = [];
        body.removeClass("hasfiles");
    };

    var fileList = $("#inimglist");
    var body = $("body");

    // Bail out if the browser doesn't support required features
    // blobbuilder and a[download] are not required, as there is a fallback
    if (!FileReaderJS.enabled || !Modernizr.draganddrop) {
        body.addClass("disabled");
    }

    // drag and drop setup.
    var opts = {
        accept: 'image/*',
        on: {
            beforestart: function(file) {
                if (file.size > App.MAX_BYTES) {
                    return false;
                }
            },
            error: function(file) {
                fileList.append("
Error
");
            },
            skip: function(file) {
                fileList.append("
Skip
");
            },
            load: function(e, file) {
                fileList.append("

");

                var originalimg = new Image();

                if (App.timeline.length === 0){
                    // TODO: do we need this?
                    // App.timeline[0].width seems to work fine when called in groupend
                    originalimg.onload = function(){
                        App.initialWidth    = originalimg.width;
                        App.initialHeight   = originalimg.height;
                        $(window).trigger('firstimage');
                    };
                }

                originalimg.src = e.target.result;
                App.timeline.push(originalimg);
            },
            groupstart: function(group) {
                body.addClass("hasfiles");
            },
            groupend: function(group) {
                // TOOOOOO SLOW
                //buildGif();
            }
        }
    };
    // the library handles most of the dnd bits.
    FileReaderJS.setupDrop(document.body, opts);
    FileReaderJS.setupClipboard(document.body, opts);

    // test to see if we can do cool download or fallback style.
        Modernizr.addTest({
            // BlobBuilder
            "blobbuilder": function() {
                return  !!(window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder);
            },
            // Blob URL support
            "bloburls": function() {
                return !!(window.URL && window.URL.createObjectURL || window.webkitURL && window.webkitURL.createObjectURL);
            },
            // download attribute
            "download": function() {
                return "download" in document.createElement("a");
            },
            // FormData
            "formdata": function() {
                return  !!(window.FormData && "append" in window.FormData.prototype);
            }
        });

        if(!Modernizr.download && $('#saveabro')) {
            var iframe = document.createElement("iframe");

            iframe.src = "http://saveasbro.com/";
            iframe.setAttribute("style","position: absolute; visibility: hidden; left: -999em;");
            iframe.id = "saveasbro";
            document.body.appendChild(iframe);
        }

    var finalImage = $("#animresult");
    $(".clear").on('click', function() {
        App.clear();
        return false;
    });

    // kick off GIF generation and download prep
    $('.play').on('click', function(e) {
        buildGif();
        return false;
    }); // eo $('.play').on('click', function(e) {

    function buildGif() {

        if (!App.timeline.length) {
            return;
        }

        var mfAnimatedGIF = new MFAnimatedGIF({
            images: App.timeline,
            delay : App.rate,
            repeat: false,

            // use dimensions from first image as default
            height: App.animWidth || App.timeline[0].height,
            width : App.animWidth || App.timeline[0].width
        });

        $('#animresult').attr('src', mfAnimatedGIF.dataURL());

        //
        // Create the download link
        //
        window.URL = window.webkitURL || window.URL;
        window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;

        var filename = "animated.gif";

        var a = document.querySelector('#downloadlink');
        a.style.display = "none";

        if(Modernizr.download && Modernizr.bloburls && Modernizr.blobbuilder) {
            a.download = filename;
            a.href = mfAnimatedGIF.binaryURL(filename);
            a.style.display = "inline-block";

            a.onclick = function(e) {
              // Need a small delay for the revokeObjectURL to work properly.
              setTimeout(function() {
                window.URL.revokeObjectURL(a.href);
              }, 1500);
            };
        } else {
            window.onmessage = function(e) {
                e = e || window.event;

                var origin = e.origin || e.domain || e.uri;
                if(origin !== "http://saveasbro.com") return;
                a.href = "http://saveasbro.com/gif/" + e.data;
                a.style.display = "inline-block";
            };

            var iframe = document.querySelector('#saveasbro');
            iframe.contentWindow.postMessage(JSON.stringify({name:filename, data: mfAnimatedGIF.dataURL(), formdata: Modernizr.formdata}),"http://saveasbro.com");
        }

    }

<commit_msg>sometimes i'm not that smart. width/height typo.<commit_after>
    // handles interfacing with GIFEncoder
    var MFAnimatedGIF = function(opts) {
        var encoder;

        var _initialize = function(opts) {

            var canvas = document.createElement("canvas");
            var context = canvas.getContext('2d');

            encoder = new GIFEncoder();
            encoder.setRepeat(opts.repeat);
            encoder.setDelay(opts.delay);
            canvas.width =  opts.width;
            canvas.height = opts.height;
            encoder.setSize(opts.width, opts.height);

            encoder.start();

            for(var i=0; i<opts.images.length; i++) {
                var animframe = opts.images[i];
                var ctx = canvas.getContext('2d');

                ctx.drawImage(animframe, 0, 0, animframe.width, animframe.height, 0, 0, canvas.width, canvas.height);

                encoder.addFrame(ctx);
            }

            encoder.finish();
        };

        var _dataURL = function() {
            return 'data:image/gif;base64,' + $.base64.encode(encoder.stream().getData());
        };

        var _binaryURL = function() {
            // Convert encoder data to binary format
            var data = encoder.stream().getData();
            var byteArray = new Uint8Array(data.length);
            for (var i = 0; i < data.length; i++) {
                byteArray[i] = data.charCodeAt(i) & 0xff;
            }

            var bb = new BlobBuilder();
            bb.append(byteArray.buffer);
            return window.URL.createObjectURL(bb.getBlob("image/gif"));
        };

        _initialize(opts);

        return {
            dataURL: _dataURL,
            binaryURL: _binaryURL
        };
    };

    var App = {};
    App.MAX_BYTES = 2*1024*1024; // 2MB
    App.initialRate = 300; // 300ms delay in frames
    App.timeline = [];

    App.clear = function() {
        finalImage.attr("src", "about:blank").hide();
        fileList.empty();
        App.timeline = [];
        body.removeClass("hasfiles");
    };

    var fileList = $("#inimglist");
    var body = $("body");

    // Bail out if the browser doesn't support required features
    // blobbuilder and a[download] are not required, as there is a fallback
    if (!FileReaderJS.enabled || !Modernizr.draganddrop) {
        body.addClass("disabled");
    }

    // drag and drop setup.
    var opts = {
        accept: 'image/*',
        on: {
            beforestart: function(file) {
                if (file.size > App.MAX_BYTES) {
                    return false;
                }
            },
            error: function(file) {
                fileList.append("
Error
");
            },
            skip: function(file) {
                fileList.append("
Skip
");
            },
            load: function(e, file) {
                fileList.append("

");

                var originalimg = new Image();

                if (App.timeline.length === 0){
                    // TODO: do we need this?
                    // App.timeline[0].width seems to work fine when called in groupend
                    originalimg.onload = function(){
                        App.initialWidth    = originalimg.width;
                        App.initialHeight   = originalimg.height;
                        $(window).trigger('firstimage');
                    };
                }

                originalimg.src = e.target.result;
                App.timeline.push(originalimg);
            },
            groupstart: function(group) {
                body.addClass("hasfiles");
            },
            groupend: function(group) {
                // TOOOOOO SLOW
                //buildGif();
            }
        }
    };
    // the library handles most of the dnd bits.
    FileReaderJS.setupDrop(document.body, opts);
    FileReaderJS.setupClipboard(document.body, opts);

    // test to see if we can do cool download or fallback style.
        Modernizr.addTest({
            // BlobBuilder
            "blobbuilder": function() {
                return  !!(window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder);
            },
            // Blob URL support
            "bloburls": function() {
                return !!(window.URL && window.URL.createObjectURL || window.webkitURL && window.webkitURL.createObjectURL);
            },
            // download attribute
            "download": function() {
                return "download" in document.createElement("a");
            },
            // FormData
            "formdata": function() {
                return  !!(window.FormData && "append" in window.FormData.prototype);
            }
        });

        if(!Modernizr.download && $('#saveabro')) {
            var iframe = document.createElement("iframe");

            iframe.src = "http://saveasbro.com/";
            iframe.setAttribute("style","position: absolute; visibility: hidden; left: -999em;");
            iframe.id = "saveasbro";
            document.body.appendChild(iframe);
        }

    var finalImage = $("#animresult");
    $(".clear").on('click', function() {
        App.clear();
        return false;
    });

    // kick off GIF generation and download prep
    $('.play').on('click', function(e) {
        buildGif();
        return false;
    }); // eo $('.play').on('click', function(e) {

    function buildGif() {

        if (!App.timeline.length) {
            return;
        }

        var mfAnimatedGIF = new MFAnimatedGIF({
            images: App.timeline,
            delay : App.rate,
            repeat: false,

            // use dimensions from first image as default
            height: App.animHeight || App.timeline[0].height,
            width : App.animWidth  || App.timeline[0].width
        });

        $('#animresult').attr('src', mfAnimatedGIF.dataURL());

        //
        // Create the download link
        //
        window.URL = window.webkitURL || window.URL;
        window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;

        var filename = "animated.gif";

        var a = document.querySelector('#downloadlink');
        a.style.display = "none";

        if(Modernizr.download && Modernizr.bloburls && Modernizr.blobbuilder) {
            a.download = filename;
            a.href = mfAnimatedGIF.binaryURL(filename);
            a.style.display = "inline-block";

            a.onclick = function(e) {
              // Need a small delay for the revokeObjectURL to work properly.
              setTimeout(function() {
                window.URL.revokeObjectURL(a.href);
              }, 1500);
            };
        } else {
            window.onmessage = function(e) {
                e = e || window.event;

                var origin = e.origin || e.domain || e.uri;
                if(origin !== "http://saveasbro.com") return;
                a.href = "http://saveasbro.com/gif/" + e.data;
                a.style.display = "inline-block";
            };

            var iframe = document.querySelector('#saveasbro');
            iframe.contentWindow.postMessage(JSON.stringify({name:filename, data: mfAnimatedGIF.dataURL(), formdata: Modernizr.formdata}),"http://saveasbro.com");
        }

    }

<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');

module.exports = function(properties) {
    var ret = {};

    _.forEach(properties, function(v, k) {
        if(!v.readOnly) {
            ret[k] = v;
        }
    });

    return ret;
};
<commit_msg>Translate enum languages<commit_after>'use strict';
var _ = require('lodash');

var i18n = {
    'en-en': 'English',
    'fi-fi': 'Finnish',
};

module.exports = function(properties) {
    var ret = {};

    _.forEach(properties, function(v, k) {
        if(!v.readOnly) {
            ret[k] = v;
        }

        if(v.enum) {
            ret[k].enumNames = v.enum.map((o) => i18n[o]);
        }
    });

    return ret;
};
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';

import ReactModal from 'react-modal';
import FuzzySearch from 'react-fuzzy';

const modalStyle = {
  content: {
    top: '100px',
    right: 'auto',
    bottom: 'auto',
    left: '50%',
    marginLeft: '-215px',
    border: 'none',
    padding: 0,
    overflow: 'visible',
  },
  overlay: {
    background: 'transparent',
    zIndex: '1',
  },
};

const formatStories = stories => {
  const formattedStories = [];
  let i = 0;
  stories.forEach(val => {
    i += 1;
    formattedStories.push({
      type: 'kind',
      value: val.kind,
      id: i,
    });

    val.stories.forEach(story => {
      i += 1;
      formattedStories.push({
        type: 'story',
        value: story,
        id: i,
        kind: val.kind,
      });
    });
  });

  return formattedStories;
};

const SuggestionWrapper = styled('div')(
  ({ selected, selectedResultStyle, resultsStyle }) =>
    selected ? selectedResultStyle : resultsStyle,
  { display: 'flex', justifyContent: 'space-between' }
);
const SuggestionValue = styled('p')({ margin: 0 });
const SuggestionKind = styled('p')({
  opacity: 0.5,
  margin: 0,
  paddingLeft: 10,
  textAlign: 'right',
});

const suggestionTemplate = (props, state, styles, clickHandler) =>
  state.results.map((val, i) => (
    <SuggestionWrapper
      selected={state.selectedIndex === i}
      {...styles}
      tabIndex={0}
      role="option"
      aria-selected={state.selectedIndex === i}
      key={`${val.value}_${val.id}`}
      onClick={() => clickHandler(i)}
    >
      <SuggestionValue>{val.value}</SuggestionValue>
      <SuggestionKind>{val.type === 'story' ? `in ${val.kind}` : 'Kind'}</SuggestionKind>
    </SuggestionWrapper>
  ));

export default class SearchBox extends React.Component {
  constructor(props) {
    super(props);

    this.onSelect = this.onSelect.bind(this);
    this.fireOnStory = this.fireOnStory.bind(this);
    this.fireOnKind = this.fireOnKind.bind(this);
    this.inputRef = this.inputRef.bind(this);
    this.onModalOpen = this.onModalOpen.bind(this);
  }

  onSelect(selected) {
    const { onClose } = this.props;
    if (selected.type === 'story') {
      this.fireOnStory(selected.value, selected.kind);
    } else {
      this.fireOnKind(selected.value);
    }
    onClose();
  }

  onModalOpen() {
    if (this.input != null) {
      this.input.focus();
    }
  }

  fireOnKind(kind) {
    const { onSelectStory } = this.props;
    if (onSelectStory) {
      onSelectStory(kind, null);
    }
  }

  fireOnStory(story, kind) {
    const { onSelectStory } = this.props;
    if (onSelectStory) {
      onSelectStory(kind, story);
    }
  }

  inputRef(fuzzy) {
    if (fuzzy != null) {
      this.input = fuzzy.refs.searchBox;
    }
  }

  render() {
    return (
      <ReactModal
        isOpen={this.props.showSearchBox}
        onAfterOpen={this.onModalOpen}
        onRequestClose={this.props.onClose}
        style={modalStyle}
        contentLabel="Search"
        shouldReturnFocusAfterClose={false}
      >
        <FuzzySearch
          list={formatStories(this.props.stories)}
          onSelect={this.onSelect}
          keys={['value', 'type']}
          resultsTemplate={suggestionTemplate}
          innerRef={inputRef => {
            this.inputRef = inputRef;
          }}
        />
      </ReactModal>
    );
  }
}

SearchBox.defaultProps = { stories: [] };

SearchBox.propTypes = {
  showSearchBox: PropTypes.bool.isRequired,
  stories: PropTypes.arrayOf(PropTypes.object),
  onSelectStory: PropTypes.func.isRequired,
  onClose: PropTypes.func.isRequired,
};
<commit_msg>FIX & REFACTOR search box<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';

import ReactModal from 'react-modal';
import FuzzySearch from 'react-fuzzy';

const modalStyle = {
  content: {
    top: '100px',
    right: 'auto',
    bottom: 'auto',
    left: '50%',
    marginLeft: '-215px',
    border: 'none',
    padding: 0,
    overflow: 'visible',
  },
  overlay: {
    background: 'transparent',
    zIndex: '1',
  },
};

const formatStories = stories => {
  const formattedStories = [];
  let i = 0;
  stories.forEach(val => {
    i += 1;
    formattedStories.push({
      type: 'kind',
      value: val.kind,
      id: i,
    });

    val.stories.forEach(story => {
      i += 1;
      formattedStories.push({
        type: 'story',
        value: story,
        id: i,
        kind: val.kind,
      });
    });
  });

  return formattedStories;
};

const SuggestionWrapper = styled('div')(
  ({ selected, selectedResultStyle, resultsStyle }) =>
    selected ? selectedResultStyle : resultsStyle,
  { display: 'flex', justifyContent: 'space-between' }
);
const SuggestionValue = styled('p')({ margin: 0 });
const SuggestionKind = styled('p')({
  opacity: 0.5,
  margin: 0,
  paddingLeft: 10,
  textAlign: 'right',
});

const suggestionTemplate = (props, state, styles, clickHandler) =>
  state.results.map((val, i) => (
    <SuggestionWrapper
      selected={state.selectedIndex === i}
      {...styles}
      tabIndex={0}
      role="option"
      aria-selected={state.selectedIndex === i}
      key={`${val.value}_${val.id}`}
      onClick={() => clickHandler(i)}
    >
      <SuggestionValue>{val.value}</SuggestionValue>
      <SuggestionKind>{val.type === 'story' ? `in ${val.kind}` : 'Kind'}</SuggestionKind>
    </SuggestionWrapper>
  ));

export default class SearchBox extends React.Component {
  onSelect = selected => {
    const { onClose } = this.props;

    if (selected.type === 'story') {
      this.fireOnStory(selected.value, selected.kind);
    } else {
      this.fireOnKind(selected.value);
    }
    onClose();
  };

  onModalOpen = () => {
    if (this.inputRef.refs.searchBox !== null) {
      this.inputRef.refs.searchBox.focus();
    }
  };

  fireOnKind = kind => {
    const { onSelectStory } = this.props;
    if (onSelectStory) {
      onSelectStory(kind, null);
    }
  };

  fireOnStory = (story, kind) => {
    const { onSelectStory } = this.props;
    if (onSelectStory) {
      onSelectStory(kind, story);
    }
  };

  render() {
    return (
      <ReactModal
        isOpen={this.props.showSearchBox}
        onAfterOpen={this.onModalOpen}
        onRequestClose={this.props.onClose}
        style={modalStyle}
        contentLabel="Search"
        shouldReturnFocusAfterClose={false}
      >
        <FuzzySearch
          list={formatStories(this.props.stories)}
          onSelect={this.onSelect}
          keys={['value', 'type']}
          resultsTemplate={suggestionTemplate}
          ref={inputRef => {
            this.inputRef = inputRef;
          }}
        />
      </ReactModal>
    );
  }
}

SearchBox.defaultProps = { stories: [] };

SearchBox.propTypes = {
  showSearchBox: PropTypes.bool.isRequired,
  stories: PropTypes.arrayOf(PropTypes.object),
  onSelectStory: PropTypes.func.isRequired,
  onClose: PropTypes.func.isRequired,
};
<|endoftext|>


<commit_before><commit_msg>fix(gatsby-dev-cli): chmod new bin files (#25194)<commit_after><|endoftext|>


<commit_before>var levelws = require('level-ws');
var JSONStream = require('JSONStream');
var skeleton = require('log-skeleton');
var zlib = require('zlib');

module.exports = function (givenOptions, callback) {

  getOptions(givenOptions, function(err, options) {

    var indexes = options.indexes;
    var log = skeleton((options) ? options.log : undefined);
    var Replicator = {};

    //ReplicateFromStream
    Replicator.replicateFromSnapShotStream = function (readStream, callback) {
      var indexesws = levelws(indexes);
      readStream.pipe(zlib.createGunzip())
        .pipe(JSONStream.parse().on('error', callback))
        .pipe(indexesws.createWriteStream())
        .on('close', callback)
        .on('error', function(err) {
          console.log(err)
        });
    };

    //ReplicateFromBatch
    Replicator.replicateFromSnapShotBatch = function (serializedDB, callback) {
      for (var i = 0; i < serializedDB.length; i++)
        serializedDB[i].type = 'put';
      indexes.batch(serializedDB, function (err) {
        if (err) return log.warn('Ooops!', err);
        log.info('Great success dear leader!');
        callback(err);
      });
    };

    //createSnapShotForStream
    Replicator.createSnapShot = function (callback) {
      callback(indexes.createReadStream()
               .pipe(JSONStream.stringify('', '\n', ''))
               .pipe(zlib.createGzip())
              );
    };

    //createSnapShotForBatch
    Replicator.createSnapShotBatch = function (callback) {
      //has to be like this in order for norch snapshotting to work
      callback
      (indexes.createReadStream()
       .pipe(JSONStream.stringify('[', ',', ']'))
       .pipe(zlib.createGzip())
      );
    };
    return callback(null, Replicator)
  })
}

var getOptions = function(givenOptions, callbacky) {
  const _ = require('lodash')
  const async = require('async')
  const bunyan = require('bunyan')
  const levelup = require('levelup')
  const leveldown = require('leveldown')
  const tv = require('term-vector')
  givenOptions = givenOptions || {}
  async.parallel([
    function(callback) {
      var defaultOps = {}
      defaultOps.deletable = true
      defaultOps.fieldedSearch = true
      defaultOps.fieldsToStore = 'all'
      defaultOps.indexPath = 'si'
      defaultOps.logLevel = 'error'
      defaultOps.nGramLength = 1
      defaultOps.separator = /[\|' \.,\-|(\n)]+/
      defaultOps.stopwords = tv.getStopwords('en').sort()
      defaultOps.log = bunyan.createLogger({
        name: 'search-index',
        level: givenOptions.logLevel || defaultOps.logLevel
      })
      callback(null, defaultOps)
    },
    function(callback){
      if (!givenOptions.indexes) {
        levelup(givenOptions.indexPath || 'si', {
          valueEncoding: 'json'
        }, function(err, db) {
          callback(null, db)
        })
      }
      else {
        callback(null, null)
      }
    }
  ], function(err, results){
    var options = _.defaults(givenOptions, results[0])
    if (results[1] != null) {
      options = _.defaults(options, results[1])
    }
    return callbacky(err, options)
  })
}

<commit_msg>Errors should not be swallowed and should go to the callback<commit_after>var levelws = require('level-ws');
var JSONStream = require('JSONStream');
var skeleton = require('log-skeleton');
var zlib = require('zlib');

module.exports = function (givenOptions, callback) {

  getOptions(givenOptions, function(err, options) {

    var indexes = options.indexes;
    var log = skeleton((options) ? options.log : undefined);
    var Replicator = {};

    //ReplicateFromStream
    Replicator.replicateFromSnapShotStream = function (readStream, callback) {
      var indexesws = levelws(indexes);
      readStream.pipe(zlib.createGunzip())
        .pipe(JSONStream.parse().on('error', callback))
        .pipe(indexesws.createWriteStream())
        .on('close', callback)
        .on('error', callback);
    };

    //ReplicateFromBatch
    Replicator.replicateFromSnapShotBatch = function (serializedDB, callback) {
      for (var i = 0; i < serializedDB.length; i++)
        serializedDB[i].type = 'put';
      indexes.batch(serializedDB, function (err) {
        if (err) return log.warn('Ooops!', err);
        log.info('Great success dear leader!');
        callback(err);
      });
    };

    //createSnapShotForStream
    Replicator.createSnapShot = function (callback) {
      callback(indexes.createReadStream()
               .pipe(JSONStream.stringify('', '\n', ''))
               .pipe(zlib.createGzip())
              );
    };

    //createSnapShotForBatch
    Replicator.createSnapShotBatch = function (callback) {
      //has to be like this in order for norch snapshotting to work
      callback
      (indexes.createReadStream()
       .pipe(JSONStream.stringify('[', ',', ']'))
       .pipe(zlib.createGzip())
      );
    };
    return callback(null, Replicator)
  })
}

var getOptions = function(givenOptions, callbacky) {
  const _ = require('lodash')
  const async = require('async')
  const bunyan = require('bunyan')
  const levelup = require('levelup')
  const leveldown = require('leveldown')
  const tv = require('term-vector')
  givenOptions = givenOptions || {}
  async.parallel([
    function(callback) {
      var defaultOps = {}
      defaultOps.deletable = true
      defaultOps.fieldedSearch = true
      defaultOps.fieldsToStore = 'all'
      defaultOps.indexPath = 'si'
      defaultOps.logLevel = 'error'
      defaultOps.nGramLength = 1
      defaultOps.separator = /[\|' \.,\-|(\n)]+/
      defaultOps.stopwords = tv.getStopwords('en').sort()
      defaultOps.log = bunyan.createLogger({
        name: 'search-index',
        level: givenOptions.logLevel || defaultOps.logLevel
      })
      callback(null, defaultOps)
    },
    function(callback){
      if (!givenOptions.indexes) {
        levelup(givenOptions.indexPath || 'si', {
          valueEncoding: 'json'
        }, function(err, db) {
          callback(null, db)
        })
      }
      else {
        callback(null, null)
      }
    }
  ], function(err, results){
    var options = _.defaults(givenOptions, results[0])
    if (results[1] != null) {
      options = _.defaults(options, results[1])
    }
    return callbacky(err, options)
  })
}

<|endoftext|>