<commit_before>// WebSync: Charts for Spreadsheets
/*global define, $, _, openpgp, escape, FileReader, JST*/

//= require templates/chart

define(['websync', '/assets/tables.js', '/assets/Chart.js'], function(WS, tables, Chart) {
  'use strict';
  var self = {};

  // Button to insert chart. Might be moved under Table tab instead.
  $('#Insert').append(" <button id='insert_chart' class='btn btn-default Chart' title='Insert Chart'></button>");

  // Insert Modal
  $(document.body).append(JST.get('templates/chart')());
  $('#insertChartModal').find('input, select').change(function(e) {
    updateModal();
  });
  var insertChart = false;
  $('#insertChartModal #insertChartBtn').on('click.Chart', function(e) {
    WS.info('Click to insert the chart.');
    insertChart = true;
  });
  $('.content').delegate('td', 'mouseup.Chart', function(e) {
    if (insertChart) {
      $('#insertChartModal .Chart').appendTo(e.currentTarget);
      insertChart = false;
    }
  });
  $('.content_container').on('click.Chart', function(e) {
    if (insertChart) {
      var sel = WS.selectionSave();
      $('#insertChartModal .Chart').appendTo(sel.endContainer);
      insertChart = false;
    }
  });
  // Create a chart in a container with options and size.
  function makeChart(container, options, size) {
      var chart = $('
' +
        (chartTitle ? '
' + _.escape(options.chartTitle) + '
' : '') +
        '
' + _.escape(options.chartYTitle) + '
' +
        '<canvas class="noresize"></canvas>' +
        '
' + _.escape(options.chartXTitle) + '
' +
        '

' +
        '
');
      var chartObj;
      chart.data('chartOptions', options);
      chart.appendTo(document.body);
      chart.css({
        height: size.height,
        width: size.width
      });
      var $canvas = chart.find('canvas');

      var timeout = -1;

      function resize() {
        var height = chart.height();
        var width = chart.width();
        var canvasWidth = (width - (options.chartYTitle ? 30 : 0));
        var canvasHeight = height - chart.find('h3:not(.y)').height() -
          (options.legend ? 20 : 0) -
          chart.find('h2').height();
        $canvas[0].width = chartObj.chart.width = canvasWidth;
        $canvas[0].height = chartObj.chart.height = canvasHeight;
        $canvas.width(canvasWidth).height(canvasHeight);
        chart.find('h3.y').width(canvasHeight);
        clearTimeout(timeout);
        timeout = setTimeout(function() {
          chartObj.reflow();
          chartObj.render();
        }, 100);
      }

      chart.on('resize.Chart', function(e) {
        resize();
      });

      var cellData = tables.getCellData(options.dataRange);
      if (_.include(['PolarArea', 'Pie', 'Doughnut'], options.type)) {
        var data = [];
        var colors = self.colorWheel(cellData[0].length, [1, 0.5]);
        _.each(cellData[0], function(v, i) {
          data.push({
            label: v,
            value: cellData[1][i],
            color: colors[i][0],
            highlight: colors[i][1]
          });
        });
      } else {
        var data = {
          labels: cellData[0].slice(options.firstRowAsTitles ? 1 : 0),
          datasets: []
        };
        var colors = self.colorWheel(cellData.length);
        _.each(cellData.slice(1), function(column, i) {
          var color = colors[i];
          var datum = {
            fillColor: 'rgba(0,0,0,0)',
            strokeColor: color,
            pointColor: color,
            pointStrokeColor: '#fff',
            pointHighlightFill: '#fff',
            pointHighlightStroke: color
          };
          if (options.firstRowAsTitles) {
            datum.data = column.slice(1);
            datum.label = column[0];
          } else {
            datum.data = column;
          }
          if (options.type === 'Bar') {
            datum.strokeColor = 'rgba(0,0,0,0)';
            datum.fillColor = color;
          }
          data.datasets.push(datum);
        });
      }
      if (container) {
        $(container).append(chart);
      }
      var ctx = chart.find('canvas')[0].getContext('2d');
      chartObj = new Chart(ctx)[options.type](data, options);
      if (options.legend) {
        options.legendTemplate = '<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%>
<span style=\"background-color:<%=segments[i].fillColor%>\"><%if(segments[i].label){%><%=segments[i].label%><%}%>
<%}%>
';
        setTimeout(function() {
          chart.find('.legend').html(chartObj.generateLegend());
        }, 100);
      }
      resize();
      return [chart, chartObj];
    }
    // Reset the chart insertion modal.
  function clearModal() {
      $('#chartType').val('Line');

      var table_count = $('.content_container table').index(tables.primaryTable()) + 1;
      var name = $(tables.table).attr('name') || ('Table ' + table_count);
      var start = tables.selectedPos();
      var end = tables.selectedPos(tables.selectionEnd);
      var queryString = name + '.' +
        tables.columnLabel(start[0]) + (start[1] + 1) + ':' +
        tables.columnLabel(end[0]) + (end[1] + 1);
      $('#dataRange').val(queryString);
      tables.clearSelect();
    }
    // Redraw the preview chart.
  function updateModal() {
    var chartType = $('#chartType').val().replace(/ /g, '');
    $('.ChartContainer .Chart').remove();
    $('#insertChartModal .options:not(.All)').hide();
    $('#insertChartModal .options.' + chartType).show();

    var options = {
      type: chartType
    };
    _.each($('#insertChartModal').find('.options.All, .options.' + chartType).find('input, select'), function(elem) {
      var inputType = $(elem).attr('type');
      if (inputType === 'checkbox') {
        options[elem.id] = elem.checked;
      } else {
        options[elem.id] = $(elem).val();
      }
    });

    makeChart($('.ChartContainer'), options, {
      width: 558,
      height: 300
    });
  }

  // Insert Chart
  $('#insert_chart').click(function(e) {
    clearModal();
    updateModal();
    $('#insertChartModal').modal('show');
  });
  WS.registerDOMException('.Chart', function(obj) {
    return {
      options: $(obj).data('chartOptions'),
      style: $(obj).attr('style'),
      class: $(obj).attr('class')
    };
  }, function(json) {
    var id = 'tmp' + (Math.random() * 10000000 | 0);
    setTimeout(function() {
      var chart = makeChart(null, json.options, {
        width: 558,
        height: 300
      });
      $('#' + id).replaceWith(chart[0]);
      chart[0].attr('style', json.style);
      chart[0].resize();
      chart[0].attr('class', json.class);
    }, 1);
    return '

';
  });

  // Create a palette of colors. Opacities can be undefined or in the format of [1, 0.5, 0].
  self.colorWheel = function(number, opacities) {
    var colors = [];

    function makeHSLA(h, s, l, a) {
      return 'hsla(' +
        (h | 0) + ',' +
        (s | 0) + '%,' +
        (l | 0) + '%,' +
        a + ')';
    };
    _.times(number, function(i) {
      var h = i / number * 360;
      var s = 100;
      var l = 60;
      if (opacities) {
        var color = _.map(opacities, function(opacity) {
          return makeHSLA(h, s, l, opacity);
        });
      } else {
        var color = makeHSLA(h, s, l, 1);
      }
      colors.push(color);
    });
    return colors;
  };

  // Code to disable the function.
  self.disable = function() {
    $('*').unbind('.Chart');
    $('*').undelegate('.Chart');
    $('.Chart').remove();
    WS.unregisterDOMException('.Chart');
  };
  // Return self so other modules can hook into this one.
  return self;
});
<commit_msg>Cleaned up charts.js and documented it<commit_after>// WebSync: Charts for Spreadsheets
/*global define, $, _, escape, FileReader, JST, document*/

//= require templates/chart

define(['websync', '/assets/tables.js', '/assets/Chart.js'], function(WS, tables, Chart) {
  'use strict';
  var exports = {

  /**
   * Create a palette of colors in hsla format. Opacities can be undefined or in the format of [1, 0.5, 0].
   *
   * @param {Number} number - the number of colors
   * @param {Array<Number>} opacities - an optional array of opacities to generate colors with.
   * @return {Array<String>}
   */
    colorWheel: function(number, opacities) {
      var colors = [];

      function makeHSLA(h, s, l, a) {
        return 'hsla(' +
          (h | 0) + ',' +
          (s | 0) + '%,' +
          (l | 0) + '%,' +
          a + ')';
      }

      _.times(number, function(i) {
        var h = i / number * 360;
        var s = 100;
        var l = 60;
        var color;
        if (opacities) {
          color = _.map(opacities, function(opacity) {
            return makeHSLA(h, s, l, opacity);
          });
        } else {
          color = makeHSLA(h, s, l, 1);
        }
        colors.push(color);
      });
      return colors;
    },

    /**
     * Create a chart in a container with options and size.
     * @param {Element} container - the container to attach to.
     * @param {Collection} options - a object with options
     * @param {Collection} size - an object with width & height values.
     */
    makeChart: function(container, options, size) {
      var chart = $('
' +
        (options.chartTitle ? '
' + _.escape(options.chartTitle) + '
' : '') +
        '
' + _.escape(options.chartYTitle) + '
' +
        '<canvas class="noresize"></canvas>' +
        '
' + _.escape(options.chartXTitle) + '
' +
        '

' +
        '
');
      var chartObj;
      chart.data('chartOptions', options);
      chart.appendTo(document.body);
      chart.css({
        height: size.height,
        width: size.width
      });
      var $canvas = chart.find('canvas');

      var timeout = -1;

      function resize() {
        var height = chart.height();
        var width = chart.width();
        var canvasWidth = (width - (options.chartYTitle ? 30 : 0));
        var canvasHeight = height - chart.find('h3:not(.y)').height() -
          (options.legend ? 20 : 0) -
          chart.find('h2').height();
        $canvas[0].width = chartObj.chart.width = canvasWidth;
        $canvas[0].height = chartObj.chart.height = canvasHeight;
        $canvas.width(canvasWidth).height(canvasHeight);
        chart.find('h3.y').width(canvasHeight);
        clearTimeout(timeout);
        timeout = setTimeout(function() {
          chartObj.reflow();
          chartObj.render();
        }, 100);
      }

      chart.on('resize.Chart', function() {
        resize();
      });

      var cellData = tables.getCellData(options.dataRange);
      var data, colors;
      if (_.include(['PolarArea', 'Pie', 'Doughnut'], options.type)) {
        data = [];
        colors = exports.colorWheel(cellData[0].length, [1, 0.5]);
        _.each(cellData[0], function(v, i) {
          data.push({
            label: v,
            value: cellData[1][i],
            color: colors[i][0],
            highlight: colors[i][1]
          });
        });
      } else {
        data = {
          labels: cellData[0].slice(options.firstRowAsTitles ? 1 : 0),
          datasets: []
        };
        colors = exports.colorWheel(cellData.length);
        _.each(cellData.slice(1), function(column, i) {
          var color = colors[i];
          var datum = {
            fillColor: 'rgba(0,0,0,0)',
            strokeColor: color,
            pointColor: color,
            pointStrokeColor: '#fff',
            pointHighlightFill: '#fff',
            pointHighlightStroke: color
          };
          if (options.firstRowAsTitles) {
            datum.data = column.slice(1);
            datum.label = column[0];
          } else {
            datum.data = column;
          }
          if (options.type === 'Bar') {
            datum.strokeColor = 'rgba(0,0,0,0)';
            datum.fillColor = color;
          }
          data.datasets.push(datum);
        });
      }
      if (container) {
        $(container).append(chart);
      }
      var ctx = chart.find('canvas')[0].getContext('2d');
      chartObj = new Chart(ctx)[options.type](data, options);
      if (options.legend) {
        options.legendTemplate = '<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%>
<span style=\"background-color:<%=segments[i].fillColor%>\"><%if(segments[i].label){%><%=segments[i].label%><%}%>
<%}%>
';
        setTimeout(function() {
          chart.find('.legend').html(chartObj.generateLegend());
        }, 100);
      }
      resize();
      return [chart, chartObj];
    },

    // Reset the chart insertion modal.
    clearModal: function() {
      $('#chartType').val('Line');

      var table_count = $('.content_container table').index(tables.primaryTable()) + 1;
      var name = $(tables.table).attr('name') || ('Table ' + table_count);
      var start = tables.selectedPos();
      var end = tables.selectedPos(tables.selectionEnd);
      var queryString = name + '.' +
        tables.columnLabel(start[0]) + (start[1] + 1) + ':' +
        tables.columnLabel(end[0]) + (end[1] + 1);
      $('#dataRange').val(queryString);
      tables.clearSelect();
    },

    // Redraw the preview chart.
    updateModal: function() {
      var chartType = $('#chartType').val().replace(/ /g, '');
      $('.ChartContainer .Chart').remove();
      $('#insertChartModal .options:not(.All)').hide();
      $('#insertChartModal .options.' + chartType).show();

      var options = {
        type: chartType
      };
      _.each($('#insertChartModal').find('.options.All, .options.' + chartType).find('input, select'), function(elem) {
        var inputType = $(elem).attr('type');
        if (inputType === 'checkbox') {
          options[elem.id] = elem.checked;
        } else {
          options[elem.id] = $(elem).val();
        }
      });

      exports.makeChart($('.ChartContainer'), options, {
        width: 558,
        height: 300
      });
    },

    // Code to disable the function.
    disable: function() {
      $('*').unbind('.Chart');
      $('*').undelegate('.Chart');
      $('.Chart').remove();
      WS.unregisterDOMException('.Chart');
    }
  };

  // Button to insert chart. Might be moved under Table tab instead.
  $('#Insert').append(" <button id='insert_chart' class='btn btn-default Chart' title='Insert Chart'></button>");

  // Insert Modal
  $(document.body).append(JST.get('templates/chart')());
  $('#insertChartModal').find('input, select').change(function() {
    exports.updateModal();
  });
  var insertChart = false;
  $('#insertChartModal #insertChartBtn').on('click.Chart', function() {
    WS.info('Click to insert the chart.');
    insertChart = true;
  });
  $('.content').delegate('td', 'mouseup.Chart', function(e) {
    if (insertChart) {
      $('#insertChartModal .Chart').appendTo(e.currentTarget);
      insertChart = false;
    }
  });
  $('.content_container').on('click.Chart', function() {
    if (insertChart) {
      var sel = WS.selectionSave();
      $('#insertChartModal .Chart').appendTo(sel.endContainer);
      insertChart = false;
    }
  });

  // Insert Chart
  $('#insert_chart').click(function() {
    exports.clearModal();
    exports.updateModal();
    $('#insertChartModal').modal('show');
  });
  WS.registerDOMException('.Chart', function(obj) {
    return {
      'options': $(obj).data('chartOptions'),
      'style': $(obj).attr('style'),
      'class': $(obj).attr('class')
    };
  }, function(json) {
    var id = 'tmp' + (Math.random() * 10000000 | 0);
    setTimeout(function() {
      var chart = exports.makeChart(null, json.options, {
        width: 558,
        height: 300
      });
      $('#' + id).replaceWith(chart[0]);
      chart[0].attr('style', json.style);
      chart[0].resize();
      chart[0].attr('class', json['class']);
    }, 1);
    return '

';
  });
  // Return exports so other modules can hook into this one.
  return exports;
});
<|endoftext|>


<commit_before>/**
 * A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
 * recently used items while discarding least recently used items when its limit
 * is reached.
 *
 * Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
 * See README.md for details.
 *
 * Illustration of the design:
 *
 *       entry             entry             entry             entry
 *       ______            ______            ______            ______
 *      | head |.newer => |      |.newer => |      |.newer => | tail |
 *      |  A   |          |  B   |          |  C   |          |  D   |
 *      |______| <= older.|______| <= older.|______| <= older.|______|
 *
 *  removed  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  <--  added
 */
function LRUCache (limit) {
  // Current size of the cache. (Read-only).
  this.size = 0;
  // Maximum number of items this cache can hold.
  this.limit = limit;
  this.oldest = this.newest = undefined;
  this._keymap = {};
}

LRUCache.prototype._markEntryAsUsed = function(entry) {
  if (entry === this.newest) {
    // Already the most recenlty used entry, so no need to update the list
    return;
  }
  // HEAD--------------TAIL
  //   <.older   .newer>
  //  <--- add direction --
  //   A  B  C  <D>  E
  if (entry.newer) {
    if (entry === this.oldest) {
      this.oldest = entry.newer;
    }
    entry.newer.older = entry.older; // C <-- E.
  }
  if (entry.older) {
    entry.older.newer = entry.newer; // C. --> E
  }
  entry.newer = undefined; // D --x
  entry.older = this.newest; // D. --> E
  if (this.newest) {
    this.newest.newer = entry; // E. <-- D
  }
  this.newest = entry;
};

/**
 * Put <value> into the cache associated with <key>. Returns the entry which was
 * removed to make room for the new entry. Otherwise undefined is returned
 * (i.e. if there was enough room already).
 */
LRUCache.prototype.put = function(key, value) {
  var entry = this._keymap[key];

  if (entry) {
    // update existing
    entry.value = value;
    this._markEntryAsUsed(entry);
    return;
  }

  // new entry
  this._keymap[key] = entry = {key:key, value:value, older:undefined, newer:undefined};

  if (this.newest) {
    // link previous tail to the new tail (entry)
    this.newest.newer = entry;
    entry.older = this.newest;
  } else {
    // we're first in -- yay
    this.oldest = entry;
  }

  // add new entry to the end of the linked list -- it's now the freshest entry.
  this.newest = entry;
  this.size++;
  if (this.size > this.limit) {
    // we hit the limit -- remove the head
    return this.shift();
  }
};

/**
 * Purge the least recently used (oldest) entry from the cache. Returns the
 * removed entry or undefined if the cache was empty.
 *
 * If you need to perform any form of finalization of purged items, this is a
 * good place to do it. Simply override/replace this function:
 *
 *   var c = new LRUCache(123);
 *   c.shift = function() {
 *     var entry = LRUCache.prototype.shift.call(this);
 *     doSomethingWith(entry);
 *     return entry;
 *   }
 */
LRUCache.prototype.shift = function() {
  // todo: handle special case when limit == 1
  var entry = this.oldest;
  if (entry) {
    if (this.oldest.newer) {
      // advance the list
      this.oldest = this.oldest.newer;
      this.oldest.older = undefined;
    } else {
      // the cache is exhausted
      this.oldest = undefined;
      this.newest = undefined;
    }
    // Remove last strong reference to <entry> and remove links from the purged
    // entry being returned:
    entry.newer = entry.older = undefined;
    // delete is slow, but we need to do this to avoid uncontrollable growth:
    delete this._keymap[entry.key];
    this.size--;
  }
  return entry;
};

/**
 * Get and register recent use of <key>. Returns the value associated with <key>
 * or undefined if not in cache.
 */
LRUCache.prototype.get = function(key, returnEntry) {
  // First, find our cache entry
  var entry = this._keymap[key];
  if (entry === undefined) return; // Not cached. Sorry.
  // As <key> was found in the cache, register it as being requested recently
  this._markEntryAsUsed(entry);
  return returnEntry ? entry : entry.value;
};

// ----------------------------------------------------------------------------
// Following code is optional and can be removed without breaking the core
// functionality.

/**
 * Check if <key> is in the cache without registering recent use. Feasible if
 * you do not want to chage the state of the cache, but only "peek" at it.
 * Returns the entry associated with <key> if found, or undefined if not found.
 */
LRUCache.prototype.find = function(key) {
  return this._keymap[key];
};

/**
 * Update the value of entry with <key>. Returns the old value, or undefined if
 * entry was not in the cache.
 */
LRUCache.prototype.set = function(key, value) {
  var oldvalue, entry = this.get(key, true);
  if (entry) {
    oldvalue = entry.value;
    entry.value = value;
  } else {
    oldvalue = this.put(key, value);
    if (oldvalue) oldvalue = oldvalue.value;
  }
  return oldvalue;
};

/**
 * Remove entry <key> from cache and return its value. Returns undefined if not
 * found.
 */
LRUCache.prototype.remove = function(key) {
  var entry = this._keymap[key];
  if (!entry) return;
  delete this._keymap[entry.key]; // need to do delete unfortunately
  if (entry.newer && entry.older) {
    // relink the older entry with the newer entry
    entry.older.newer = entry.newer;
    entry.newer.older = entry.older;
  } else if (entry.newer) {
    // remove the link to us
    entry.newer.older = undefined;
    // link the newer entry to head
    this.oldest = entry.newer;
  } else if (entry.older) {
    // remove the link to us
    entry.older.newer = undefined;
    // link the newer entry to head
    this.newest = entry.older;
  } else {// if(entry.older === undefined && entry.newer === undefined) {
    this.oldest = this.newest = undefined;
  }

  this.size--;
  return entry.value;
};

/** Removes all entries */
LRUCache.prototype.removeAll = function() {
  // This should be safe, as we never expose strong refrences to the outside
  this.oldest = this.newest = undefined;
  this.size = 0;
  this._keymap = {};
};

/**
 * Return an array containing all keys of entries stored in the cache object, in
 * arbitrary order.
 */
if (typeof Object.keys === 'function') {
  LRUCache.prototype.keys = function() {
    return Object.keys(this._keymap);
  };
} else {
  LRUCache.prototype.keys = function() {
    var keys = [];
    for (var k in this._keymap) {
      keys.push(k);
    }
    return keys;
  };
}

/**
 * Call `fun` for each entry. Starting with the newest entry if `desc` is a true
 * value, otherwise starts with the oldest (head) enrty and moves towards the
 * tail.
 *
 * `fun` is called with 3 arguments in the context `context`:
 *   `fun.call(context, Object key, Object value, LRUCache self)`
 */
LRUCache.prototype.forEach = function(fun, context, desc) {
  var entry;
  if (context === true) {
    desc = true;
    context = undefined;
  } else if (typeof context !== 'object') {
    context = this;
  }
  if (desc) {
    entry = this.newest;
    while (entry) {
      fun.call(context, entry.key, entry.value, this);
      entry = entry.older;
    }
  } else {
    entry = this.oldest;
    while (entry) {
      fun.call(context, entry.key, entry.value, this);
      entry = entry.newer;
    }
  }
};

/** Returns a JSON (array) representation */
LRUCache.prototype.toJSON = function() {
  var s = new Array(this.size), i = 0, entry = this.oldest;
  while (entry) {
    s[i++] = { key: entry.key, value: entry.value };
    entry = entry.newer;
  }
  return s;
};

/** Returns a String representation */
LRUCache.prototype.toString = function() {
  var s = '', entry = this.oldest;
  while (entry) {
    s += String(entry.key)+':'+entry.value;
    entry = entry.newer;
    if (entry) {
      s += ' < ';
    }
  }
  return s;
};

// Export ourselves
if (typeof this === 'object') this.LRUCache = LRUCache;
<commit_msg>lru is now managed via npm<commit_after><|endoftext|>


<commit_before>import { VALIDATE_OPTIONS, addLocation, combineErrorDetails, serversExist } from './utils';

import joi from 'joi';

const schema = joi.object().keys({
  name: joi.string().min(1).required(),
  path: joi.string().min(1).required(),
  port: joi.number(),
  servers: joi.object().required(),
  deployCheckWaitTime: joi.number(),
  deployCheckPort: joi.number(),
  enableUploadProgressBar: joi.bool(),
  dockerImage: joi.string(),
  docker: joi.object().keys({
    image: joi.string().trim(),
    imageFrontendServer: joi.string(),
    args: joi.array().items(joi.string().label('docker.args array items')),
    bind: joi.string().trim(),
    networks: joi.array().items(
      joi.string().label('docker.networks array items')
    )
  }),
  buildOptions: joi.object().keys({
    serverOnly: joi.bool(),
    debug: joi.bool(),
    cleanAfterBuild: joi.bool(),
    buildLocation: joi.string(),
    mobileSettings: joi.object(),
    server: joi.string().uri(),
    allowIncompatibleUpdates: joi.boolean(),
    executable: joi.string()
  }),
  env: joi
    .object()
    .keys({
      ROOT_URL: joi
        .string()
        .regex(new RegExp('^(http|https)://', 'i'))
        .required(),
      MONGO_URL: joi.string()
    })
    .pattern(/[\s\S]*/, [joi.string(), joi.number()]),
  log: joi.object().keys({
    driver: joi.string(),
    opts: joi.object()
  }),
  volumes: joi.object(),
  nginx: joi.object().keys({
    clientUploadLimit: joi.string().trim()
  }),
  ssl: joi
    .object()
    .keys({
      autogenerate: joi
        .object()
        .keys({
          email: joi.string().email().required(),
          domains: joi.string().required()
        })
        .label('autogenerate'),
      crt: joi.string().trim(),
      key: joi.string().trim(),
      port: joi.number()
    })
    .and('crt', 'key')
    .without('autogenerate', ['crt', 'key'])
    .or('crt', 'autogenerate')
});

export default function(config) {
  let details = [];
  details = combineErrorDetails(
    details,
    joi.validate(config.meteor, schema, VALIDATE_OPTIONS)
  );
  if (config.meteor.name.indexOf(' ') > -1) {
    details.push({
      message: '"name" has a space'
    });
  }
  details = combineErrorDetails(
    details,
    serversExist(config.servers, config.meteor.servers)
  );

  return addLocation(details, 'meteor');
}
<commit_msg>Improve validator message for ROOT_URL<commit_after>import {
  VALIDATE_OPTIONS,
  addLocation,
  combineErrorDetails,
  serversExist
} from './utils';

import joi from 'joi';

const schema = joi.object().keys({
  name: joi.string().min(1).required(),
  path: joi.string().min(1).required(),
  port: joi.number(),
  servers: joi.object().required(),
  deployCheckWaitTime: joi.number(),
  deployCheckPort: joi.number(),
  enableUploadProgressBar: joi.bool(),
  dockerImage: joi.string(),
  docker: joi.object().keys({
    image: joi.string().trim(),
    imageFrontendServer: joi.string(),
    args: joi.array().items(joi.string().label('docker.args array items')),
    bind: joi.string().trim(),
    networks: joi
      .array()
      .items(joi.string().label('docker.networks array items'))
  }),
  buildOptions: joi.object().keys({
    serverOnly: joi.bool(),
    debug: joi.bool(),
    cleanAfterBuild: joi.bool(),
    buildLocation: joi.string(),
    mobileSettings: joi.object(),
    server: joi.string().uri(),
    allowIncompatibleUpdates: joi.boolean(),
    executable: joi.string()
  }),
  env: joi
    .object()
    .keys({
      ROOT_URL: joi
        .string()
        .regex(
          new RegExp('^(http|https)://', 'i'),
          'valid url with "http://" or "https://"'
        )
        .required(),
      MONGO_URL: joi.string()
    })
    .pattern(/[\s\S]*/, [joi.string(), joi.number()]),
  log: joi.object().keys({
    driver: joi.string(),
    opts: joi.object()
  }),
  volumes: joi.object(),
  nginx: joi.object().keys({
    clientUploadLimit: joi.string().trim()
  }),
  ssl: joi
    .object()
    .keys({
      autogenerate: joi
        .object()
        .keys({
          email: joi.string().email().required(),
          domains: joi.string().required()
        })
        .label('autogenerate'),
      crt: joi.string().trim(),
      key: joi.string().trim(),
      port: joi.number()
    })
    .and('crt', 'key')
    .without('autogenerate', ['crt', 'key'])
    .or('crt', 'autogenerate')
});

export default function(config) {
  let details = [];
  details = combineErrorDetails(
    details,
    joi.validate(config.meteor, schema, VALIDATE_OPTIONS)
  );
  if (config.meteor.name.indexOf(' ') > -1) {
    details.push({
      message: '"name" has a space'
    });
  }
  details = combineErrorDetails(
    details,
    serversExist(config.servers, config.meteor.servers)
  );

  return addLocation(details, 'meteor');
}
<|endoftext|>


<commit_before>export default class VersionedStore {
  /**
   * Data store that keeps track of the last properties that were changed
   * @constructor
   * @param {Object} [validProperties=null] - An object containing valid property names as keys and that property's default value as values
   */
  constructor(validProperties=null) {
    this._data = Object.assign({}, validProperties || {});

    // {changedProperty: oldValue, ...}
    this._changes = {};

    this._validPropertiesNames = new Set(Object.keys(validProperties));
  }

  /**
   * Gets a copy of the value associated with the given name
   * @param {string} name
   */
  get(name) {
    this._assertValidProperty(name);

    return this._data[name];
  }

  /**
   * Stores the given value and tracks its old value as changed
   * @param {string} name
   * @param value
   */
  set(name, value) {
    this._assertValidProperty(name);

    if (this._data.hasOwnProperty(name)) {
      this._changes[name] = this._data[name];
    }

    this._data[name] = value;
  }

  /**
   * Retrieves a list of the names of the properties that have changed
   */
  getChangedPropertyNames() {
    return Object.keys(this._changes);
  }

  /**
   * Retrieves an object containing the name and old value
   * of each property that has been changed
   */
  getChangedOldValues() {
    return Object.assign({}, this._changes);
  }

  /**
   * Retrieves an object containing the name and current values
   * of each property that has been changed
   */
  getChangedCurrentValues() {
    const changed = {};

    for (let propName of this._changes) {
      changed[propName] = this.get(propName);
    }

    return changed;
  }

  /**
   * Clears out any recorded changes
   */
  clearChanges() {
    this._changes = {};
  }

  _assertValidProperty(name) {
    if (this._validPropertiesNames && !this._validPropertiesNames.has(name)) {
      throw new Error("Cannot retrieve property '" + name + "'");
    }
  }
}
<commit_msg>Fixed two bugs in versioned store with the construtor the for of loop going through changes<commit_after>export default class VersionedStore {
  /**
   * Data store that keeps track of the last properties that were changed
   * @constructor
   * @param {Object} [validProperties=null] - An object containing valid property names as keys and that property's default value as values
   */
  constructor(validProperties=null) {
    this._data = Object.assign({}, validProperties || {});

    // {changedProperty: oldValue, ...}
    this._changes = {};

    this._validPropertiesNames = null;
    if (validProperties) {
      this._validPropertiesNames = new Set(Object.keys(validProperties));
    }
  }

  /**
   * Gets a copy of the value associated with the given name
   * @param {string} name
   */
  get(name) {
    this._assertValidProperty(name);

    return this._data[name];
  }

  /**
   * Stores the given value and tracks its old value as changed
   * @param {string} name
   * @param value
   */
  set(name, value) {
    this._assertValidProperty(name);

    if (this._data.hasOwnProperty(name)) {
      this._changes[name] = this._data[name];
    }

    this._data[name] = value;
  }

  /**
   * Retrieves a list of the names of the properties that have changed
   */
  getChangedPropertyNames() {
    return Object.keys(this._changes);
  }

  /**
   * Retrieves an object containing the name and old value
   * of each property that has been changed
   */
  getChangedOldValues() {
    return Object.assign({}, this._changes);
  }

  /**
   * Retrieves an object containing the name and current values
   * of each property that has been changed
   */
  getChangedCurrentValues() {
    const changed = {};

    for (let propName of Object.keys(this._changes)) {
      changed[propName] = this.get(propName);
    }

    return changed;
  }

  /**
   * Clears out any recorded changes
   */
  clearChanges() {
    this._changes = {};
  }

  _assertValidProperty(name) {
    if (this._validPropertiesNames && !this._validPropertiesNames.has(name)) {
      throw new Error("Cannot retrieve property '" + name + "'");
    }
  }
}
<|endoftext|>


<commit_before>/*! videojs-markers !*/

(function($,undefined) {
   //default setting
   var default_setting = {
      markerStyle:{
         'width':'7px',
         'border-radius': '30%',
         'background-color': 'red'
      },
      markerTip:{
         display: true,
         default_text: "Break",
         show_colon: true
      },
      breakOverlay:{
         display: false,
         display_time: 3,
         default_text: "Break overlay",
         show_colon: true,
         style:{
            'width':'100%',
            'height': '20%',
            'background-color': 'rgba(0,0,0,0.7)',
            'color': 'white',
            'font-size': '17px'
         }
      },
      forceInitialization: false
   };

   function registerVideoJsMarkersPlugin() {
      // check that videojs is loaded
      if (!(typeof videojs == 'function')) {
         setTimeout(function() { registerVideoJsMarkersPlugin(); }, 100);
      } else {
         /**
          * register the markers plugin (dependent on jquery)
          */
         videojs.plugin('markers', function(options) {

            var markers       = [],
               setting       = $.extend(true, {}, default_setting, options.setting),
               video_wrapper = $(this.el()),
               player        = this;
            options.marker_text   = options.marker_text || [];
            options.marker_breaks = options.marker_breaks || [];

            function createMarkers(){
               // create the markers
               var duration, m, pos, text;
               console.log("[videojs-markers] creating markers");
               duration = player.duration();
               $.each(options.marker_breaks, function(key,time){
                  pos = (time/duration)*100;
                  m = $("

");
                  m.css(setting.markerStyle)
                     .css({"margin-left"   : -parseFloat(m.css("width"))/2 +'px',
                        "left"          : pos+ '%'});

                  video_wrapper.find('.vjs-progress-control').append(m);
                  text = options.marker_text[key] || "";
                  markers.push({div: m, time: time, pos:pos, text: text});
               });
            }

            function displayMarkerTip(){
               var marker_tip;
               console.log("[videojs-markers] creating marker tip");
               marker_tip = $("

");
               video_wrapper.find('.vjs-progress-control').append(marker_tip);

               video_wrapper.find('.vjs-marker').on('mouseover', function(){
                  var id = this.id;
                  marker_tip.find('.vjs-tip-inner').html(setting.markerTip.default_text + (setting.markerTip.show_colon ? ":" : "") + " " + markers[id].text);

                  //margin-left needs to minus the padding length to align right now the markers
                  marker_tip.css({"left"        : markers[id].pos+'%',
                     "margin-left" : -parseFloat(marker_tip.css("width"))/2-5 +'px',
                     "visibility"  : "visible"});

               }).on('mouseout',function(){
                     marker_tip.css("visibility", "hidden");
               });
            }

            function displayBreakOverlay(){
               var break_overlay, ct, overlay_index;
               console.log("[videojs-markers] creating break overlay");
               break_overlay = $("

")
                  .css(setting.breakOverlay.style);
               video_wrapper.append(break_overlay);
               overlay_index = -1;

               //bind timeupdate handle
               player.on("timeupdate", function() {
                  ct = player.currentTime();
                  if(overlay_index == -1){
                     //check if playback enters any break period
                     $.each(markers, function(key, m){
                        if(ct >= m.time && ct <= (m.time+setting.breakOverlay.display_time)){
                           overlay_index = key;
                           break_overlay.find('.vjs-break-overlay-text').html(setting.breakOverlay.default_text + (setting.breakOverlay.show_colon ? ":" : "") + " " + (markers[overlay_index].text));
                           break_overlay.css("visibility", "visible");
                           return false;
                        }
                     });
                  }else{
                     //overlay is on, check if we left the break period yet
                     if(ct < markers[overlay_index].time ||
                        ct > markers[overlay_index].time+setting.breakOverlay.display_time){
                        overlay_index = -1;
                        break_overlay.css("visibility", "hidden");
                     }
                  }
               });
            }

            //load the markers
            function initializeMarkers(){
               if (player.duration() == 0) {
                  setTimeout(function() { initializeMarkers(); }, 100);
               } else {
                  console.log("[videojs-markers] Initialize");
                  createMarkers();
                  console.log("[videojs-markers] markers");
                  console.log(markers);
                  //bind click event to seek to marker time
                  video_wrapper.find('.vjs-marker').on('click', function(e){
                     player.currentTime(markers[this.id].time);
                  });
                  if(setting.markerTip.display){
                     displayMarkerTip();
                  }
                  if(setting.breakOverlay.display){
                     displayBreakOverlay();
                  }
               }
            }

            if(setting.forceInitialization){
               initializeMarkers();
            } else {
                this.on("loadedmetadata", function(){
                   initializeMarkers();
                });
            }

         });
      }
   }

   registerVideoJsMarkersPlugin();

})(jQuery);<commit_msg>Resolves Issue #6: Unique ID for Marker DIVs   - Added a MARKER_ID_DELIMITER constant   - Set the ID of the marker element to a concatenation of the ID of the video_wrapper element, the delimiter above, and the array index   - Changed the mouseover and click events to parse the element for the index using the above delimiter<commit_after>/*! videojs-markers !*/

(function($,undefined) {
   //default setting
   var default_setting = {
      markerStyle:{
         'width':'7px',
         'border-radius': '30%',
         'background-color': 'red'
      },
      markerTip:{
         display: true,
         default_text: "Break",
         show_colon: true
      },
      breakOverlay:{
         display: false,
         display_time: 3,
         default_text: "Break overlay",
         show_colon: true,
         style:{
            'width':'100%',
            'height': '20%',
            'background-color': 'rgba(0,0,0,0.7)',
            'color': 'white',
            'font-size': '17px'
         }
      },
      forceInitialization: false
   };
   var MARKER_ID_DELIMITER = '-videojsmarker-';

   function registerVideoJsMarkersPlugin() {
      // check that videojs is loaded
      if (!(typeof videojs == 'function')) {
         setTimeout(function() { registerVideoJsMarkersPlugin(); }, 100);
      } else {
         /**
          * register the markers plugin (dependent on jquery)
          */
         videojs.plugin('markers', function(options) {

            var markers       = [],
               setting       = $.extend(true, {}, default_setting, options.setting),
               video_wrapper = $(this.el()),
               player        = this;
            options.marker_text   = options.marker_text || [];
            options.marker_breaks = options.marker_breaks || [];

            function createMarkers(){
               // create the markers
               var duration, m, pos, text;
               console.log("[videojs-markers] creating markers");
               duration = player.duration();
               $.each(options.marker_breaks, function(key,time){
                  pos = (time/duration)*100;
                  m = $("

");
                  m.css(setting.markerStyle)
                     .css({"margin-left"   : -parseFloat(m.css("width"))/2 +'px',
                        "left"          : pos+ '%'});

                  video_wrapper.find('.vjs-progress-control').append(m);
                  text = options.marker_text[key] || "";
                  markers.push({div: m, time: time, pos:pos, text: text});
               });
            }

            function displayMarkerTip(){
               var marker_tip;
               console.log("[videojs-markers] creating marker tip");
               marker_tip = $("

");
               video_wrapper.find('.vjs-progress-control').append(marker_tip);

               video_wrapper.find('.vjs-marker').on('mouseover', function(){
                  var id = this.id.split(MARKER_ID_DELIMITER)[1];
                  marker_tip.find('.vjs-tip-inner').html(setting.markerTip.default_text + (setting.markerTip.show_colon ? ":" : "") + " " + markers[id].text);

                  //margin-left needs to minus the padding length to align right now the markers
                  marker_tip.css({"left"        : markers[id].pos+'%',
                     "margin-left" : -parseFloat(marker_tip.css("width"))/2-5 +'px',
                     "visibility"  : "visible"});

               }).on('mouseout',function(){
                     marker_tip.css("visibility", "hidden");
               });
            }

            function displayBreakOverlay(){
               var break_overlay, ct, overlay_index;
               console.log("[videojs-markers] creating break overlay");
               break_overlay = $("

")
                  .css(setting.breakOverlay.style);
               video_wrapper.append(break_overlay);
               overlay_index = -1;

               //bind timeupdate handle
               player.on("timeupdate", function() {
                  ct = player.currentTime();
                  if(overlay_index == -1){
                     //check if playback enters any break period
                     $.each(markers, function(key, m){
                        if(ct >= m.time && ct <= (m.time+setting.breakOverlay.display_time)){
                           overlay_index = key;
                           break_overlay.find('.vjs-break-overlay-text').html(setting.breakOverlay.default_text + (setting.breakOverlay.show_colon ? ":" : "") + " " + (markers[overlay_index].text));
                           break_overlay.css("visibility", "visible");
                           return false;
                        }
                     });
                  }else{
                     //overlay is on, check if we left the break period yet
                     if(ct < markers[overlay_index].time ||
                        ct > markers[overlay_index].time+setting.breakOverlay.display_time){
                        overlay_index = -1;
                        break_overlay.css("visibility", "hidden");
                     }
                  }
               });
            }

            //load the markers
            function initializeMarkers(){
               if (player.duration() == 0) {
                  setTimeout(function() { initializeMarkers(); }, 100);
               } else {
                  console.log("[videojs-markers] Initialize");
                  createMarkers();
                  console.log("[videojs-markers] markers");
                  console.log(markers);
                  //bind click event to seek to marker time
                  video_wrapper.find('.vjs-marker').on('click', function(e){
                     var id = this.id.split(MARKER_ID_DELIMITER)[1];
                     player.currentTime(markers[id].time);
                  });
                  if(setting.markerTip.display){
                     displayMarkerTip();
                  }
                  if(setting.breakOverlay.display){
                     displayBreakOverlay();
                  }
               }
            }

            if(setting.forceInitialization){
               initializeMarkers();
            } else {
                this.on("loadedmetadata", function(){
                   initializeMarkers();
                });
            }

         });
      }
   }

   registerVideoJsMarkersPlugin();

})(jQuery);<|endoftext|>


<commit_before>import './typedefs'
import {meetsCondition} from './utils/conditionals'
import _ from 'lodash'
import Immutable from 'seamless-immutable'

/**
 * Function used for filtering out undefined values from arrays.
 *
 * @param {any} item Item we want to check the value of
 * @returns {boolean} True if the provided value is not undefined
 */
function isNotUndefined (item) {
  return item !== undefined
}

/**
 * Check a list conditions for a cell against a provided value
 *
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @returns {Function} Function that returns true if a condition has been met
 */
function checkConditions (value) {
  return function (condition) {
    const metCondition = conditionItem =>
        _.every(conditionItem, (condition, propName) =>
          meetsCondition(value.get(propName), condition)
        )

    if (condition.unless) {
      const unless = condition.unless.find(metCondition)
      if (unless !== undefined) {
        return false
      }
    }
    if (condition.if) {
      return isNotUndefined(condition.if.find(metCondition))
    } else {
      return true
    }
  }
}

/**
 * Check the root cells of a view
 *
 * @param {BunsenView} view View we are checking
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @returns {Function} Iterator function to check cells
 */
function checkRootCells (view, value) {
  return function (cell) {
    return checkCell(view, value, Immutable.from(cell)).asMutable()
  }
}

/**
 * Check a cell for conditions and apply any conditional properties if a condition is met.
 *
 * @param {BunsenView} view View the cell is a part of
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @param {BunsenCell} cell Cell to check
 * @returns {BunsenCell} The cell after conditions have been processed. If a condition is not met undefined is returned
 */
function checkCellConditions (view, value, cell) {
  // Find a condition that has been met
  const meetsCondition = checkConditions(value)
  const condition = cell.conditions.find(meetsCondition)
  if (condition === undefined) {
    // Returns undefined if conditions aren't met so we can filter
    return
  }

  if (condition.then) { // Cell has conditional properties, so add them
    cell = Immutable.merge(cell, condition.then)
  }
  return cell
}

/**
 * Copy properties from an extended cell. Extends child cells recrusively.
 *
 * @param {BunsenView} view View the cell is a part of
 * @param {BunsenCell} cell Cell to copy properties onto
 * @returns {BunsenCell} Resulting cell after applying properties from extended cells
 */
function expandExtendedCell (view, cell) {
  const cellProps = {}
  let extendedCell = view.cellDefinitions[cell.extends]
  if (extendedCell.extends) {
    extendedCell = Immutable.without(expandExtendedCell(view, extendedCell), 'extends')
  }

  if (extendedCell.itemCell && extendedCell.itemCell.extends) {
    cellProps.itemCell = expandExtendedCell(view, extendedCell.itemCell)
  }

  if (extendedCell.children) {
    cellProps.children = extendedCell.children.map(child => {
      if (child.extends) {
        return expandExtendedCell(view, child)
      }
      return child
    })
  }

  return Immutable.merge(cell, extendedCell, cellProps)
}

/**
 * Check a cell of a view to make sure the value meets any conditions the cell provides
 *
 * @param {BunsenView} view View we are checking
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @param {BunsenCell} cell Cell to check
 * @returns {BunsenCell} Cell with properties from any extended cells
 */
function checkCell (view, value, cell) {
  if (cell.extends) {
    cell = expandExtendedCell(view, cell)
  }

  value = value.pushPath(cell.model)

  if (cell.conditions) { // This cell has conditions
    cell = checkCellConditions(view, value, cell)
    if (cell === undefined) {
      return
    }
  }

  if (cell.children) {
    cell = checkChildren(view, value, cell)
  }

  return Immutable.without(cell, 'conditions', 'extends')
}

/**
 * Check conditions of a cell's children
 *
 * @param {BunsenView} view View we are checking
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @param {BunsenCell} cell Cell to check
 * @returns {BunsenCell} Cell with the children checked
 */
function checkChildren (view, value, cell) {
  const children = _.map(cell.children, (child) => {
    return checkCell(view, value, child)
  })
  .filter(isNotUndefined)

  return Immutable.set(cell, 'children', children)
}

/**
 * Apply conditions (and extensions) to the cells of a view
 *
 * @export
 * @param {BunsenView} view View to process
 * @param {any} value The value we want to check conditions against
 * @returns {BunsenView} View after conditions have been applied
 */
export default function evaluateView (view, value) {
  const wrappedValue = new ValueWrapper(value, [])
  const immutableView = Immutable.from(view)

  const cells = _.chain(view.cells)
    .map(checkRootCells(immutableView, wrappedValue))
    .filter(isNotUndefined)
    .value()

  return Object.assign(_.clone(view), {
    cells
  })
}
/* eslint-disable complexity */
/**
 * Find how many path elements we have to go back in order to find the absolute path
 *
 * @param {string[]} path Array of path elements. THIS ARRAY IS MUTATED BY THE FUNCTION
 * @param {number} [index=0] How many elements we've already gone back
 * @returns {number} How many elements back we need go
 */
function findRelativePath (path, index = 0) {
  let nextInPath = _.last(path)

  if (nextInPath === '') { // . for sibling
    if (index <= 0) {
      index += 1
    }
    path.pop()
    if (_.last(path) === '') { // .. for sibling of parent
      path.pop()
      nextInPath = path.pop().replace('/', '')// get rid of leading slash
      if (nextInPath === '') {
        return findRelativePath(path, index + 1)
      }
      path.push(nextInPath)
      return index + 1
    } else {
      nextInPath = path.pop().replace('/', '')// get rid of leading slash
      if (nextInPath === '') {
        return findRelativePath(path, index)
      }
      path.push(nextInPath)
      return index
    }
  }
}
/* eslint-enable complexity */

/**
 * Class to wrap value objects to find values based on relative and absolute paths
 *
 * @class ValueWrapper
 */
class ValueWrapper {
  static pathAsArray (path) {
    if (!Array.isArray(path)) {
      return path.split('.')
    }
    return path
  }

  constructor (value, curPath) {
    this.value = value
    this.path = ValueWrapper.pathAsArray(curPath)
  }

  /**
   * Get the value at an absolute or relative path. Paths with a leading './' or '../' are treated as relative,
   * and others are treated as absolute.
   *
   * Relative paths are relative to a stored path. To add to the path use the pushPath method.
   *
   * @param {string | string[]} path Path to the desired value.
   * @returns {any} Value at the given path.
   *
   * @memberOf ValueWrapper
   */
  get (path) {
    let absolutePath
    if (path === undefined) {
      if (this.path.length <= 0) {
        return this.value
      }
      return _.get(this.value, this.path.join('.'))
    }
    path = ValueWrapper.pathAsArray(path)

    let nextInPath = _.first(path)

    if (nextInPath === '') {
      let index = findRelativePath(path.reverse())
      absolutePath = this.path.slice(0, this.path.length - index).concat(path)
    } else {
      absolutePath = path
    }

    return _.get(this.value, absolutePath.join('.'))
  }

  /**
   * Creates another value wrapper with a relative path
   *
   * @param {string | string[]} path Element(s) to add to the currently stored path
   * @returns {ValueWrapper} A value wrapper with the new path elements
   *
   * @memberOf ValueWrapper
   */
  pushPath (path) {
    if (path === undefined) {
      return this
    }
    path = ValueWrapper.pathAsArray(path)
    return new ValueWrapper(this.value, this.path.concat(path))
  }
}
<commit_msg>Bug fixes for view conditions<commit_after>import './typedefs'
import {meetsCondition} from './utils/conditionals'
import _ from 'lodash'
import Immutable from 'seamless-immutable'

/**
 * Function used for filtering out undefined values from arrays.
 *
 * @param {any} item Item we want to check the value of
 * @returns {boolean} True if the provided value is not undefined
 */
function isNotUndefined (item) {
  return item !== undefined
}

/**
 * Check a list conditions for a cell against a provided value
 *
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @returns {Function} Function that returns true if a condition has been met
 */
function checkConditions (value) {
  return function (condition) {
    const metCondition = conditionItem =>
        _.every(conditionItem, (condition, propName) =>
          meetsCondition(value.get(propName), condition)
        )

    if (condition.unless) {
      const unless = condition.unless.find(metCondition)
      if (unless !== undefined) {
        return false
      }
    }
    if (condition.if) {
      return isNotUndefined(condition.if.find(metCondition))
    } else {
      return true
    }
  }
}

/**
 * Check the root cells of a view
 *
 * @param {BunsenView} view View we are checking
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @returns {Function} Iterator function to check cells
 */
function checkRootCells (view, value) {
  return function (cell) {
    return checkCell(view, value, Immutable.from(cell)).asMutable()
  }
}

/**
 * Check a cell for conditions and apply any conditional properties if a condition is met.
 *
 * @param {BunsenView} view View the cell is a part of
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @param {BunsenCell} cell Cell to check
 * @returns {BunsenCell} The cell after conditions have been processed. If a condition is not met undefined is returned
 */
function checkCellConditions (view, value, cell) {
  // Find a condition that has been met
  const meetsCondition = checkConditions(value)
  const condition = cell.conditions.find(meetsCondition)
  if (condition === undefined) {
    // Returns undefined if conditions aren't met so we can filter
    return
  }

  if (condition.then) { // Cell has conditional properties, so add them
    cell = Immutable.merge(cell, condition.then)
  }
  return cell
}

/**
 * Copy properties from an extended cell. Extends child cells recrusively.
 *
 * @param {BunsenView} view View the cell is a part of
 * @param {BunsenCell} cell Cell to copy properties onto
 * @returns {BunsenCell} Resulting cell after applying properties from extended cells
 */
function expandExtendedCell (view, cell) {
  const cellProps = {}
  let extendedCell = _.get(view.cellDefinitions, cell.extends)
  if (extendedCell.extends) {
    extendedCell = Immutable.without(expandExtendedCell(view, extendedCell), 'extends')
  }

  if (extendedCell.itemCell && extendedCell.itemCell.extends) {
    cellProps.itemCell = expandExtendedCell(view, extendedCell.itemCell)
  }

  if (extendedCell.children) {
    cellProps.children = extendedCell.children.map(child => {
      if (child.extends) {
        return expandExtendedCell(view, child)
      }
      return child
    })
  }

  return Immutable.merge(cell, extendedCell, cellProps)
}

/**
 * Check a cell of a view to make sure the value meets any conditions the cell provides
 *
 * @param {BunsenView} view View we are checking
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @param {BunsenCell} cell Cell to check
 * @returns {BunsenCell} Cell with properties from any extended cells
 */
function checkCell (view, value, cell) {
  if (cell.extends) {
    cell = expandExtendedCell(view, cell)
  }

  value = value.pushPath(cell.model)

  if (cell.conditions) { // This cell has conditions
    cell = checkCellConditions(view, value, cell)
    if (cell === undefined) {
      return
    }
  }

  if (cell.children) {
    cell = checkChildren(view, value, cell)
  }

  return Immutable.without(cell, 'conditions', 'extends')
}

/**
 * Check conditions of a cell's children
 *
 * @param {BunsenView} view View we are checking
 * @param {ValueWrapper} value The wrapped value we want to check the conditions against
 * @param {BunsenCell} cell Cell to check
 * @returns {BunsenCell} Cell with the children checked
 */
function checkChildren (view, value, cell) {
  const children = _.map(cell.children, (child) => {
    return checkCell(view, value, child)
  })
  .filter(isNotUndefined)

  return Immutable.set(cell, 'children', children)
}

/**
 * Apply conditions (and extensions) to the cells of a view
 *
 * @export
 * @param {BunsenView} view View to process
 * @param {any} value The value we want to check conditions against
 * @returns {BunsenView} View after conditions have been applied
 */
export default function evaluateView (view, value) {
  const wrappedValue = new ValueWrapper(value, [])
  const immutableView = Immutable.from(view)
  if (view.cells === undefined) {
    return view
  }
  try {
    const cells = _.chain(view.cells)
      .map(checkRootCells(immutableView, wrappedValue))
      .filter(isNotUndefined)
      .value()

    return Object.assign(_.clone(view), {
      cells
    })
  } catch (e) {
    // Unfortunately this is necessary because view validation happens outside of the reducer,
    // so we have no guarantee that the view is valid and it may cause run time errors. Returning
    return view
  }
}
/* eslint-disable complexity */
/**
 * Find how many path elements we have to go back in order to find the absolute path
 *
 * @param {string[]} path Array of path elements. THIS ARRAY IS MUTATED BY THE FUNCTION
 * @param {number} [index=0] How many elements we've already gone back
 * @returns {number} How many elements back we need go
 */
function findRelativePath (path, index = 0) {
  let nextInPath = _.last(path)

  if (nextInPath === '') { // . for sibling
    if (index <= 0) {
      index += 1
    }
    path.pop()
    if (_.last(path) === '') { // .. for sibling of parent
      path.pop()
      nextInPath = path.pop().replace('/', '')// get rid of leading slash
      if (nextInPath === '') {
        return findRelativePath(path, index + 1)
      }
      path.push(nextInPath)
      return index + 1
    } else {
      nextInPath = path.pop().replace('/', '')// get rid of leading slash
      if (nextInPath === '') {
        return findRelativePath(path, index)
      }
      path.push(nextInPath)
      return index
    }
  }
}
/* eslint-enable complexity */

/**
 * Class to wrap value objects to find values based on relative and absolute paths
 *
 * @class ValueWrapper
 */
class ValueWrapper {
  static pathAsArray (path) {
    if (!Array.isArray(path)) {
      return path.split('.')
    }
    return path
  }

  constructor (value, curPath) {
    this.value = value
    this.path = ValueWrapper.pathAsArray(curPath)
  }

  /**
   * Get the value at an absolute or relative path. Paths with a leading './' or '../' are treated as relative,
   * and others are treated as absolute.
   *
   * Relative paths are relative to a stored path. To add to the path use the pushPath method.
   *
   * @param {string | string[]} path Path to the desired value.
   * @returns {any} Value at the given path.
   *
   * @memberOf ValueWrapper
   */
  get (path) {
    let absolutePath
    if (path === undefined) {
      if (this.path.length <= 0) {
        return this.value
      }
      return _.get(this.value, this.path.join('.'))
    }
    path = ValueWrapper.pathAsArray(path)

    let nextInPath = _.first(path)

    if (nextInPath === '') {
      let index = findRelativePath(path.reverse())
      absolutePath = this.path.slice(0, this.path.length - index).concat(path)
    } else {
      absolutePath = path
    }

    return _.get(this.value, absolutePath.join('.'))
  }

  /**
   * Creates another value wrapper with a relative path
   *
   * @param {string | string[]} path Element(s) to add to the currently stored path
   * @returns {ValueWrapper} A value wrapper with the new path elements
   *
   * @memberOf ValueWrapper
   */
  pushPath (path) {
    if (path === undefined) {
      return this
    }
    path = ValueWrapper.pathAsArray(path)
    return new ValueWrapper(this.value, this.path.concat(path))
  }
}
<|endoftext|>


<commit_before>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }

  this._fbref.on("child_added", this._childAdded, this);
  this._fbref.on("child_moved", this._childMoved, this);
  this._fbref.on("child_changed", this._childChanged, this);
  this._fbref.on("child_removed", this._childRemoved, this);
};

_.extend(Backbone.Firebase.prototype, {

  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },
  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },
  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },
  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.ref().push().name();
    }
    var val = model.toJSON();
    this._fbref.ref().child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }
    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });
    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.ref().child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  'delete': function(model, cb) {
    this._fbref.ref().child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == 'function') {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(store, [model, function(err, val) {
    if (err) {
      model.trigger("error", model, err, options);
      if (Backbone.VERSION === "0.9.10") {
        options.error(model, err, options);
      } else {
        options.error(err);
      }
    } else {
      model.trigger("sync", model, val, options);
      if (Backbone.VERSION === "0.9.10") {
        options.success(model, val, options);
      } else {
        options.success(val);
      }
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override 'Backbone.sync' to default to Firebase sync.
// the original 'Backbone.sync' is still available in 'Backbone.oldSync'
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
  return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("child_added", _.bind(this._childAdded, this));
    this.firebase.on("child_moved", _.bind(this._childMoved, this));
    this.firebase.on("child_changed", _.bind(this._childChanged, this));
    this.firebase.on("child_removed", _.bind(this._childRemoved, this));

    this.listenTo(this, 'change', this._updateModel, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(model, _.bind(options.success, model));
    }
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(null, _.bind(options.success, model));
    }
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      if (!model.id) {
        model.id = this.firebase.ref().push().name();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    var model = snap.val()
    if (!model.id) {
      if (!_.isObject(model)) model = {};
      model.id = snap.name()
    }
    Backbone.Collection.prototype.add.apply(this, [model]);
    this.get(model.id)._remoteAttributes = model;
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    if (!model.id) model.id = snap.name()
    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });
    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }

    this._preventSync(item, true);
    item._remoteAttributes = model;

    var diff = _.difference(_.keys(item.attributes), _.keys(model));
    _.each(diff, function(key) {
      item.unset(key);
    });

    item.set(model);

    this._preventSync(item, false);
  },

  _childRemoved: function(snap) {
    var model = snap.val()
    if (!model.id) model.id = snap.name()
    Backbone.Collection.prototype.remove.apply(this, [model]);
  },

  // Add handlers for all models in this collection, and any future ones
  // that may be added.
  _updateModel: function(model, options) {

    if (model._remoteChanging) return;

    var remoteAttributes = model._remoteAttributes || {};
    var localAttributes = model.toJSON();
    var updateAttributes = {};

    _.each(_.union(_.keys(remoteAttributes),_.keys(localAttributes)), function(key) {
      if (!_.has(localAttributes, key)) {
        updateAttributes[key] = null;
      }
      else if (localAttributes[key] != remoteAttributes[key]) {
        updateAttributes[key] = localAttributes[key];
      }
    });

    if (_.size(updateAttributes)) {
      this.firebase.ref().child(model.id).update(updateAttributes);
    }
  },

  _preventSync: function(model, state) {
    model._remoteChanging = state;
  }
});

// Custom Firebase Model.
Backbone.Firebase.Model = Backbone.Model.extend({
  save: function() {
    this._log("Save called on a Firebase model, ignoring.");
  },

  destroy: function(options) {
    // TODO: Fix naive success callback. Add error callback.
    this.firebase.ref().set(null, this._log);
    this.trigger('destroy', this, this.collection, options);
    if (options.success) {
      options.success(this,null,options);
    }
  },

  constructor: function(model, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Model.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("value", this._modelChanged.bind(this));

    this._listenLocalChange(true);
  },

  _listenLocalChange: function(state) {
    if (state)
      this.on("change", this._updateModel, this);
    else
      this.off("change", this._updateModel, this);
  },

  _updateModel: function(model, options) {
    // Find the deleted keys and set their values to null
    // so Firebase properly deletes them.
    var modelObj = model.toJSON();
    _.each(model.changed, function(value, key) {
      if (typeof value === "undefined" || value === null)
        if (key == "id")
          delete modelObj[key];
        else
          modelObj[key] = null;
    });
    if (_.size(modelObj))
      this.firebase.ref().update(modelObj, this._log);
  },

  _modelChanged: function(snap) {
    // Unset attributes that have been deleted from the server
    // by comparing the keys that have been removed.
    var newModel = snap.val();
    if (typeof newModel === "object" && newModel !== null) {
      var diff = _.difference(_.keys(this.attributes), _.keys(newModel));
      var _this = this;
      _.each(diff, function(key) {
          _this.unset(key);
      });
    }
    this._listenLocalChange(false);
    this.set(newModel);
    this.trigger('sync', this, null, null);
    this._listenLocalChange(true);
  },

  _log: function(msg) {
    if (typeof msg === "undefined" || msg === null) return;
    if (console && console.log) {
      console.log(msg);
    }
  }

});

})();<commit_msg>Update only attributes that have changed.<commit_after>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }

  this._fbref.on("child_added", this._childAdded, this);
  this._fbref.on("child_moved", this._childMoved, this);
  this._fbref.on("child_changed", this._childChanged, this);
  this._fbref.on("child_removed", this._childRemoved, this);
};

_.extend(Backbone.Firebase.prototype, {

  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },
  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },
  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },
  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.ref().push().name();
    }
    var val = model.toJSON();
    this._fbref.ref().child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }
    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });
    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.ref().child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  'delete': function(model, cb) {
    this._fbref.ref().child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == 'function') {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(store, [model, function(err, val) {
    if (err) {
      model.trigger("error", model, err, options);
      if (Backbone.VERSION === "0.9.10") {
        options.error(model, err, options);
      } else {
        options.error(err);
      }
    } else {
      model.trigger("sync", model, val, options);
      if (Backbone.VERSION === "0.9.10") {
        options.success(model, val, options);
      } else {
        options.success(val);
      }
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override 'Backbone.sync' to default to Firebase sync.
// the original 'Backbone.sync' is still available in 'Backbone.oldSync'
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
  return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("child_added", _.bind(this._childAdded, this));
    this.firebase.on("child_moved", _.bind(this._childMoved, this));
    this.firebase.on("child_changed", _.bind(this._childChanged, this));
    this.firebase.on("child_removed", _.bind(this._childRemoved, this));

    this.listenTo(this, 'change', this._updateModel, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(model, _.bind(options.success, model));
    }
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(null, _.bind(options.success, model));
    }
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      if (!model.id) {
        model.id = this.firebase.ref().push().name();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    var model = snap.val()
    if (!model.id) {
      if (!_.isObject(model)) model = {};
      model.id = snap.name()
    }
    Backbone.Collection.prototype.add.apply(this, [model]);
    this.get(model.id)._remoteAttributes = model;
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    if (!model.id) model.id = snap.name()
    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });
    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }

    this._preventSync(item, true);
    item._remoteAttributes = model;

    var diff = _.difference(_.keys(item.attributes), _.keys(model));
    _.each(diff, function(key) {
      item.unset(key);
    });

    item.set(model);

    this._preventSync(item, false);
  },

  _childRemoved: function(snap) {
    var model = snap.val()
    if (!model.id) model.id = snap.name()
    Backbone.Collection.prototype.remove.apply(this, [model]);
  },

  // Add handlers for all models in this collection, and any future ones
  // that may be added.
  _updateModel: function(model, options) {

    if (model._remoteChanging) return;

    var remoteAttributes = model._remoteAttributes || {};
    var localAttributes = model.toJSON();
    var updateAttributes = {};

    _.each(_.union(_.keys(remoteAttributes),_.keys(localAttributes)), function(key) {
      if (!_.has(localAttributes, key)) {
        updateAttributes[key] = null;
      }
      else if (localAttributes[key] != remoteAttributes[key]) {
        updateAttributes[key] = localAttributes[key];
      }
    });

    if (_.size(updateAttributes)) {
      this.firebase.ref().child(model.id).update(updateAttributes);
    }
  },

  _preventSync: function(model, state) {
    model._remoteChanging = state;
  }
});

// Custom Firebase Model.
Backbone.Firebase.Model = Backbone.Model.extend({
  save: function() {
    this._log("Save called on a Firebase model, ignoring.");
  },

  destroy: function(options) {
    // TODO: Fix naive success callback. Add error callback.
    this.firebase.ref().set(null, this._log);
    this.trigger('destroy', this, this.collection, options);
    if (options.success) {
      options.success(this,null,options);
    }
  },

  constructor: function(model, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Model.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("value", this._modelChanged.bind(this));

    this._listenLocalChange(true);
  },

  _listenLocalChange: function(state) {
    if (state)
      this.on("change", this._updateModel, this);
    else
      this.off("change", this._updateModel, this);
  },

  _updateModel: function(model, options) {
    // Find the deleted keys and set their values to null
    // so Firebase properly deletes them.
    var modelObj = model.changedAttributes();
    _.each(model.changed, function(value, key) {
      if (typeof value === "undefined" || value === null)
        if (key == "id")
          delete modelObj[key];
        else
          modelObj[key] = null;
    });
    if (_.size(modelObj))
      this.firebase.ref().update(modelObj, this._log);
  },

  _modelChanged: function(snap) {
    // Unset attributes that have been deleted from the server
    // by comparing the keys that have been removed.
    var newModel = snap.val();
    if (typeof newModel === "object" && newModel !== null) {
      var diff = _.difference(_.keys(this.attributes), _.keys(newModel));
      var _this = this;
      _.each(diff, function(key) {
          _this.unset(key);
      });
    }
    this._listenLocalChange(false);
    this.set(newModel);
    this.trigger('sync', this, null, null);
    this._listenLocalChange(true);
  },

  _log: function(msg) {
    if (typeof msg === "undefined" || msg === null) return;
    if (console && console.log) {
      console.log(msg);
    }
  }

});

})();
<|endoftext|>


<commit_before>// Backbone.Mixpanel v0.0.1
// (c) 2014 | Chetan Shenoy - me@chetanshenoy.com
// MIT License

// Combination of Backbone Mixpanel implementations
// inspired by Brian Norton & Swizec Teller
// https://github.com/bnorton/backbone-mixpanel
// http://swizec.com/blog/mixing-mixpanel-into-backbone/swizec/6498

Backbone.View.originalDelegateEvents = Backbone.View.prototype.delegateEvents;

Backbone.View.prototype.delegateEvents = function(events) {
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
  if (!(events || (events = _.result(this, 'events')))) return this;

  var track = _.bind(function (event) {
    var $target = $(event.currentTarget),
        event_name = $target.data('events'),
        data = !!this.model ? this.model.toJSON() : {};

    if (event_name) {
      mixpanel.track(event_name, data);
    }
  }, this);

  for (var key in events) {
    var match = key.match(delegateEventSplitter),
        type = match[1],
        selector = match[2];

    if (type == 'click' || type == 'submit') {
      if (selector === "") {
        this.$el.on(type, track);
      } else{
        this.$el.on(type, selector, track);
      }
    }
  }

  return Backbone.View.originalDelegateEvents.call(this, events);
};
<commit_msg>Convert to UMD<commit_after>// Backbone.Mixpanel v0.0.1
// (c) 2014 | Chetan Shenoy - me@chetanshenoy.com
// MIT License

// Combination of Backbone Mixpanel implementations
// inspired by Brian Norton & Swizec Teller
// https://github.com/bnorton/backbone-mixpanel
// http://swizec.com/blog/mixing-mixpanel-into-backbone/swizec/6498

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(['backbone', 'underscore', 'jquery'], function (Backbone, _, $) {
      return (root.returnExportsGlobal = factory(Backbone, _, $));
    });
  } else if (typeof exports === 'object') {
    var _ = require('underscore'), $;
    try { $ = require('jquery'); } catch(e) {}
    module.exports = factory(require('backbone'), _, $);
  } else {
    // Global Variables
    root.returnExportsGlobal = factory(root.Backbone, root._, root.$);
  }
}(this, function (Backbone, _, $) {
  Backbone.View.originalDelegateEvents = Backbone.View.prototype.delegateEvents;

  Backbone.View.prototype.delegateEvents = function(events) {
    var delegateEventSplitter = /^(\S+)\s*(.*)$/;
    if (!(events || (events = _.result(this, 'events')))) return this;

    var track = _.bind(function (event) {
      var $target = $(event.currentTarget),
          event_name = $target.data('events'),
          data = !!this.model ? this.model.toJSON() : {};

      if (event_name) {
        mixpanel.track(event_name, data);
      }
    }, this);

    for (var key in events) {
      var match = key.match(delegateEventSplitter),
          type = match[1],
          selector = match[2];

      if (type == 'click' || type == 'submit') {
        if (selector === "") {
          this.$el.on(type, track);
        } else{
          this.$el.on(type, selector, track);
        }
      }
    }

    return Backbone.View.originalDelegateEvents.call(this, events);
  };
}));
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import { Link } from 'react-router';
import {
  LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid,
} from 'recharts';
import Highlight from 'utils/Highlight';
import { getLocaleType, localeGet } from '../utils/LocaleUtils';
import './IndexView.scss';
import 'simple-line-icons/scss/simple-line-icons.scss';
import users from '../docs/users/users';

const data = [
  {
    name: 'A', uv: 400, pv: 240, amt: 2400,
  },
  {
    name: 'B', uv: 300, pv: 456, amt: 2400,
  },
  {
    name: 'C', uv: 300, pv: 139, amt: 2400,
  },
  {
    name: 'D', uv: 200, pv: 980, amt: 2400,
  },
  {
    name: 'E', uv: 278, pv: 390, amt: 2400,
  },
  {
    name: 'F', uv: 189, pv: 480, amt: 2400,
  },
];

class IndexView extends PureComponent {
  render() {
    const locale = getLocaleType(this.props);

    const exCode = `
  <LineChart width={500} height={300} data={data}>
    <XAxis dataKey="name"/>
    <YAxis/>
    <CartesianGrid stroke="#eee" strokeDasharray="5 5"/>
    <Line type="monotone" dataKey="uv" stroke="#8884d8" />
    <Line type="monotone" dataKey="pv" stroke="#82ca9d" />
  </LineChart>
    `;

    return (

Recharts

{localeGet(locale, 'home', 'solgan')}

            <Link to={`/${locale}/guide/installation`} className="button install-btn">

              {' '}
              {localeGet(locale, 'home', 'install')}
              {' '}
v1.6.2
            </Link>

            <LineChart
              width={500}
              height={300}
              data={data}
              margin={{
                top: 5, right: 5, bottom: 5, left: 0,
              }}
            >
              <CartesianGrid stroke="#eee" strokeDasharray="5 5" />
              <XAxis dataKey="name" />
              <YAxis />
              <Line type="monotone" dataKey="uv" stroke="#8884d8" />
              <Line type="monotone" dataKey="pv" stroke="#82ca9d" />
            </LineChart>
            <Highlight className="e4x">{exCode}</Highlight>

{localeGet(locale, 'home', 'feature')}

              {localeGet(locale, 'home', 'composable')}

{localeGet(locale, 'home', 'composableContent')}

              {localeGet(locale, 'home', 'reliable')}

{localeGet(locale, 'home', 'reliableContent')}

              {localeGet(locale, 'home', 'powerful')}

{localeGet(locale, 'home', 'powerfulContent')}

{localeGet(locale, 'home', 'whoUse')}

            {
            users.map((entry, index) => (
              <li className="user" key={`user-${index}`}>

            ))
          }

{localeGet(locale, 'home', 'logoDesc')}

              {localeGet(locale, 'home', 'addUser')}

                {localeGet(locale, 'home', 'edit')}

{localeGet(locale, 'home', 'logoSize')}

{localeGet(locale, 'home', 'like')}

<Link to={`/${locale}/guide/getting-started`} className="button getting-started-btn">{localeGet(locale, 'home', 'get-started')}</Link>

    );
  }
}

export default IndexView;
<commit_msg>feat: update version<commit_after>import React, { PureComponent } from 'react';
import { Link } from 'react-router';
import {
  LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid,
} from 'recharts';
import Highlight from 'utils/Highlight';
import { getLocaleType, localeGet } from '../utils/LocaleUtils';
import './IndexView.scss';
import 'simple-line-icons/scss/simple-line-icons.scss';
import users from '../docs/users/users';

const data = [
  {
    name: 'A', uv: 400, pv: 240, amt: 2400,
  },
  {
    name: 'B', uv: 300, pv: 456, amt: 2400,
  },
  {
    name: 'C', uv: 300, pv: 139, amt: 2400,
  },
  {
    name: 'D', uv: 200, pv: 980, amt: 2400,
  },
  {
    name: 'E', uv: 278, pv: 390, amt: 2400,
  },
  {
    name: 'F', uv: 189, pv: 480, amt: 2400,
  },
];

class IndexView extends PureComponent {
  render() {
    const locale = getLocaleType(this.props);

    const exCode = `
  <LineChart width={500} height={300} data={data}>
    <XAxis dataKey="name"/>
    <YAxis/>
    <CartesianGrid stroke="#eee" strokeDasharray="5 5"/>
    <Line type="monotone" dataKey="uv" stroke="#8884d8" />
    <Line type="monotone" dataKey="pv" stroke="#82ca9d" />
  </LineChart>
    `;

    return (

Recharts

{localeGet(locale, 'home', 'solgan')}

            <Link to={`/${locale}/guide/installation`} className="button install-btn">

              {' '}
              {localeGet(locale, 'home', 'install')}
              {' '}
v1.8.5
            </Link>

            <LineChart
              width={500}
              height={300}
              data={data}
              margin={{
                top: 5, right: 5, bottom: 5, left: 0,
              }}
            >
              <CartesianGrid stroke="#eee" strokeDasharray="5 5" />
              <XAxis dataKey="name" />
              <YAxis />
              <Line type="monotone" dataKey="uv" stroke="#8884d8" />
              <Line type="monotone" dataKey="pv" stroke="#82ca9d" />
            </LineChart>
            <Highlight className="e4x">{exCode}</Highlight>

{localeGet(locale, 'home', 'feature')}

              {localeGet(locale, 'home', 'composable')}

{localeGet(locale, 'home', 'composableContent')}

              {localeGet(locale, 'home', 'reliable')}

{localeGet(locale, 'home', 'reliableContent')}

              {localeGet(locale, 'home', 'powerful')}

{localeGet(locale, 'home', 'powerfulContent')}

{localeGet(locale, 'home', 'whoUse')}

            {
            users.map((entry, index) => (
              <li className="user" key={`user-${index}`}>

            ))
          }

{localeGet(locale, 'home', 'logoDesc')}

              {localeGet(locale, 'home', 'addUser')}

                {localeGet(locale, 'home', 'edit')}

{localeGet(locale, 'home', 'logoSize')}

{localeGet(locale, 'home', 'like')}

<Link to={`/${locale}/guide/getting-started`} className="button getting-started-btn">{localeGet(locale, 'home', 'get-started')}</Link>

    );
  }
}

export default IndexView;
<|endoftext|>


<commit_before>import * as types from '../mutation-types'
import * as token from '../../utils/token'

// initial module state
const state = {
	authForm: 'login', // 'login' or 'register'
	isAuthenticated: false,
	authMessage: { tone: '', message: '' } // tone can be 'danger' or 'success'
}

// mutations
const mutations = {

	[types.LOGIN_SUCCESS] (state, response) {
		loginOrRegistrationSuccess(response)
	},

	[types.LOGIN_FAILURE] (state, response) {
		token.destroy()
		setAuthMessage('danger', response.errorMessage)
	},

	[types.LOGOUT_SUCCESS] (state, response) {
		logoutSuccessOrFailure()
	},

	[types.LOGOUT_FAILURE] (state, response) {
		logoutSuccessOrFailure()
	},

	[types.REGISTRATION_SUCCESS] (state, response) {
		loginOrRegistrationSuccess(response)
	},

	[types.REGISTRATION_FAILURE] (state, response) {
		setAuthMessage('danger', response.errorMessage)
	},

	[types.SET_AUTH_MESSAGE] (state, tone, message) {
		setAuthMessage(tone, message)
	},

	[types.TOOGLE_AUTH_FORM] (state, form) {
		setAuthForm(form)
	},

	[types.SET_IS_AUTHENTICATED] (state, boolean) {
		state.isAuthenticated = boolean
	}
}

function setAuthForm(form) {
	state.authForm = form
}

function logoutSuccessOrFailure() {
	token.destroy()
	setAuthForm('login')
	setIsAuthenticated(false)
	localStorage.automaticLogin = false
	setAuthMessage('success', 'Successfully logged out.')
}

function loginOrRegistrationSuccess(response) {
	token.set(response.token)
}

function setIsAuthenticated(boolean) {
	state.isAuthenticated = boolean
}

function setAuthMessage(tone, message) {
	state.authMessage = {
		tone: tone,
		message: message
	}
}

export default {
  state,
  mutations
}

<commit_msg>testing reload to reset data on logout<commit_after>import * as types from '../mutation-types'
import * as token from '../../utils/token'

// initial module state
const state = {
	authForm: 'login', // 'login' or 'register'
	isAuthenticated: false,
	authMessage: { tone: '', message: '' } // tone can be 'danger' or 'success'
}

// mutations
const mutations = {

	[types.LOGIN_SUCCESS] (state, response) {
		loginOrRegistrationSuccess(response)
	},

	[types.LOGIN_FAILURE] (state, response) {
		token.destroy()
		setAuthMessage('danger', response.errorMessage)
	},

	[types.LOGOUT_SUCCESS] (state, response) {
		logoutSuccessOrFailure()
	},

	[types.LOGOUT_FAILURE] (state, response) {
		logoutSuccessOrFailure()
	},

	[types.REGISTRATION_SUCCESS] (state, response) {
		loginOrRegistrationSuccess(response)
	},

	[types.REGISTRATION_FAILURE] (state, response) {
		setAuthMessage('danger', response.errorMessage)
	},

	[types.SET_AUTH_MESSAGE] (state, tone, message) {
		setAuthMessage(tone, message)
	},

	[types.TOOGLE_AUTH_FORM] (state, form) {
		setAuthForm(form)
	},

	[types.SET_IS_AUTHENTICATED] (state, boolean) {
		state.isAuthenticated = boolean
	}
}

function setAuthForm(form) {
	state.authForm = form
}

function logoutSuccessOrFailure() {
	token.destroy()
	setAuthForm('login')
	setIsAuthenticated(false)
	localStorage.automaticLogin = false
	setAuthMessage('success', 'Successfully logged out.')
	location.reload()
}

function loginOrRegistrationSuccess(response) {
	token.set(response.token)
}

function setIsAuthenticated(boolean) {
	state.isAuthenticated = boolean
}

function setAuthMessage(tone, message) {
	state.authMessage = {
		tone: tone,
		message: message
	}
}

export default {
  state,
  mutations
}

<|endoftext|>