<commit_before>/**
 * @fileoverview Provides a D3js component to be used to draw an elevation
 * profile chart.
 *
 * Example usage:
 *
 * var selection = d3.select('#element_id');
 * var profile = ngeo.profile({
 *  elevationExtractor: {
 *    z: function (item) {return item['values']['z'];)},
 *    dist: function (item) {return item['dist'];)}
 *  },
 *  hoverCallback: function(point) {
 *    console.log(point.x, point.y);
 *  },
 *  outCallback: function() {
 *    console.log("out");
 *  }});
 * selection.datum(data).call(profile);
 *
 * The selection data must be an array.
 * The layout for the items of this array is unconstrained: the elevation
 * and distance values are extracted using the elevationExtractor config
 * option. Optionally, POIs can be displayed and depend on a poiExtractor
 * config option.
 *
 * The data below will work for the above example:
 * [
 *     {
 *         "y": 199340,
 *         "values": {"z": 788.7},
 *         "dist": 0.0,
 *         "x": 541620
 *     }, ...
 * ]
 */
goog.provide('ngeo.profile');

/**
 *
 * @return {Object}
 * @param {ngeox.profile.ProfileOptions} options
 * @export
 *
 */
ngeo.profile = function(options) {
  /**
   * @type {boolean}
   * Whether the simplified profile should be shown.
   */
  var light = goog.isDef(options.light) ? options.light : false;

  /**
   * The values for margins around the chart defined in pixels.
   */
  var margin = light ? {top: 0, right: 0, bottom: 0, left: 0} :
      {top: 10, right: 10, bottom: 30, left: 40};

  /**
   * Method to get the coordinate in pixels from a distance.
   */
  var bisectDistance = d3.bisector(function(d) {
    return elevationExtractor.dist(d);
  }).left;

  /**
   * Distance units. Either 'm' or 'km'.
   */
  var units;

  /**
   * Factor to determine whether to use 'm' or 'km'.
   */
  var xFactor;

  /**
   * @type {function(Object)}
   * Hover callback function.
   */
  var hoverCallback = goog.isDef(options.hoverCallback) ?
      options.hoverCallback : goog.nullFunction;

  /**
   * @type {function()}
   * Out callback function.
   */
  var outCallback = goog.isDef(options.outCallback) ?
      options.outCallback : goog.nullFunction;

  /**
   * The color to be used for filling the area.
   * Can be overriden using the '.area' CSS selector.
   * @type {string}
   */
  var fill_color = 'rgba(222, 222, 222, 0.5)';

  /**
   * The color to be used the line stroke.
   * Can be overriden using the '.line' CSS selector.
   * @type {string}
   */
  var stroke_color = '#F00';

  /**
   * @type {Object}
   */
  var svg;

  /**
   * D3 x scale.
   */
  var x;

  /**
   * D3 y scale.
   */
  var y;

  /**
   * Elevation data extractor used to get the dist and elevation values.
   */
  var elevationExtractor = options.elevationExtractor;

  /**
   * POI data extractor.
   */
  var poiExtractor = options.poiExtractor;

  var profile = function(selection) {
    selection.each(function(data) {
      var extractor = elevationExtractor;

      var width = this.clientWidth - margin.right - margin.left;
      x = d3.scale.linear().range([0, width]);

      var height = this.clientHeight - margin.top - margin.bottom;
      y = d3.scale.linear().range([height, 0]);

      var xAxis = d3.svg.axis().scale(x).orient('bottom');
      var yAxis = d3.svg.axis()
          .scale(y)
          .orient('left');

      var area = d3.svg.area()
          .x(function(d) { return x(extractor.dist(d)); })
          .y0(height)
          .y1(function(d) { return y(extractor.z(d)); });
      var line = d3.svg.line()
          .x(function(d) { return x(extractor.dist(d)); })
          .y(function(d) { return y(extractor.z(d)); });

      // Select the svg element, if it exists.
      svg = d3.select(this).selectAll('svg').data([data]);
      clearPois();

      // Otherwise, create the skeletal chart.
      var gEnter = svg.enter().append('svg').append('g');
      gEnter.style('font', '11px Arial');
      gEnter.append('path').attr('class', 'area')
          .style('fill', fill_color);
      gEnter.append('path').attr('class', 'line')
          .style('stroke', stroke_color)
          .style('fill', 'none');

      gEnter.insert('g', ':first-child')
          .attr('class', 'grid-y');

      if (!light) {
        gEnter.append('g')
          .attr('class', 'x axis')
          .attr('transform', 'translate(0,' + height + ')');

        gEnter.append('text')
          .attr('class', 'x label')
          .attr('text-anchor', 'end')
          .attr('x', width - 4)
          .attr('y', height - 4);

        gEnter.append('g')
          .attr('class', 'y axis');

        gEnter.append('text')
          .attr('class', 'y label')
          .attr('text-anchor', 'end')
          .attr('y', 6)
          .attr('dy', '.75em')
          .attr('transform', 'rotate(-90)')
          .style('fill', 'grey')
          .text('elevation (m)');

        gEnter.append('g')
          .attr('class', 'metas')
          .attr('transform', 'translate(' + (width + 3) + ', 0)');
      }

      gEnter.append('g').attr('class', 'pois');

      var yHover = gEnter.append('g').attr('class', 'y grid-hover');
      yHover.append('svg:line').attr('stroke-dasharray', '5,5');
      yHover.append('text');

      var xHover = gEnter.append('g').attr('class', 'x grid-hover');
      xHover.append('svg:line').attr('stroke-dasharray', '5,5');
      xHover.append('text');

      gEnter.append('rect')
          .attr('class', 'overlay')
          .attr('width', width)
          .attr('height', height)
          .style('fill', 'none')
          .style('pointer-events', 'all');

      // Update the outer dimensions.
      svg.attr('width', width + margin.left + margin.right)
          .attr('height', height + margin.top + margin.bottom);

      // Update the inner dimensions.
      var g = svg.select('g')
          .attr('transform', 'translate(' + margin.left + ',' +
              margin.top + ')');

      var xDomain = d3.extent(data, function(d) { return extractor.dist(d); });
      x.domain(xDomain);

      var yDomain = [d3.min(data, function(d) { return extractor.z(d); }),
            d3.max(data, function(d) { return extractor.z(d); })];

      var padding = (yDomain[1] - yDomain[0]) * 0.1;
      y.domain([yDomain[0] - padding, yDomain[1] + padding]);

      // Update the area path.
      g.select('.area')
          .transition()
          .attr('d', area);
      g.select('.line')
          .transition()
          .attr('d', line);

      if (xDomain[1] > 2000) {
        xFactor = 1000;
        units = 'km';
      } else {
        xFactor = 1;
        units = 'm';
      }

      if (!light) {
        xAxis.tickFormat(function(d) {
          return d / xFactor;
        });

        g.select('.x.axis')
          .transition()
          .call(xAxis);

        g.select('.x.label')
          .text('distance (' + units + ')')
          .style('fill', 'grey')
          .style('shape-rendering', 'crispEdges');

        g.select('.y.axis')
          .transition()
          .call(yAxis);
      }

      g.select('.grid-y')
          .transition()
          .call(yAxis.tickSize(-width, 0, 0).tickFormat(''))
          .selectAll('.tick line')
          .style('stroke', '#ccc')
          .style('opacity', 0.7);

      g.selectAll('.axis').selectAll('path, line')
          .style('fill', 'none')
          .style('stroke', '#000')
          .style('shape-rendering', 'crispEdges');

      g.selectAll('.grid-hover line')
          .style('stroke', '#222')
          .style('opacity', 0.8);

      g.select('.overlay')
          .on('mouseout', mouseout)
          .on('mousemove', mousemove);

      function mousemove() {
        var mouseX = d3.mouse(this)[0];
        var x0 = x.invert(mouseX);
        var i = bisectDistance(data, x0, 1);

        var point = data[i];
        var elevation = extractor.z(point);
        var dist = extractor.dist(point);

        g.select('.x.grid-hover')
            .style('display', 'inline')
            .select('line')
            .attr('x1', x(dist))
            .attr('y1', height)
            .attr('x2', x(dist))
            .attr('y2', y(elevation));

        g.select('.y.grid-hover')
            .style('display', 'inline')
            .select('line')
            .attr('x1', x(0))
            .attr('y1', y(elevation))
            .attr('x2', width)
            .attr('y2', y(elevation));

        var right = x0 > xDomain[1] / 2;
        var xtranslate = x(dist);
        xtranslate += right ? -10 : 10;

        g.select('.x.grid-hover text')
            .text(parseFloat(dist.toPrecision(3) / xFactor) + ' ' + units)
            .style('text-anchor', right ? 'end' : 'start')
            .attr('transform', 'translate(' + xtranslate + ',' +
                (height - 10) + ')');

        g.select('.y.grid-hover text')
            .text(Math.round(elevation) + ' m')
            .style('text-anchor', right ? 'end' : 'start')
            .attr('transform', 'translate(' + xtranslate + ',' +
                (y(elevation) - 10) + ')');
        hoverCallback.call(null, point);
      }

      function mouseout() {
        g.selectAll('.grid-hover')
            .style('display', 'none');
        outCallback.call(null);
      }
    });
  };

  profile.showPois = function(pois) {
    clearPois();
    if (!goog.isDef(pois)) {
      return;
    }

    var pe = poiExtractor;
    var g = svg.select('g');
    var profileData = svg.datum();
    var ps = g.select('.pois');

    var p = ps.selectAll('.poi').data(pois, function(d) {
      var i = bisectDistance(profileData, Math.round(pe.dist(d) * 10) / 10, 1);
      var point = profileData[i];
      if (point) {
        var z = elevationExtractor.z(point);
        pe.z(d, z);
      }
      return pe.id(d);
    });

    var poiEnter = p.enter()
      .append('g')
      .attr('class', 'poi');

    ps.selectAll('.poi')
      .style('opacity', 0)
      .transition()
      .duration(1000)
      .delay(100)
      .style('opacity', 1);

    poiEnter
      .append('text')
      .attr('x', light ? 0 : 9)
      .attr('dy', '.35em')
      .attr('text-anchor', light ? 'middle' : 'start')
      .attr('transform', function(d) {
          if (light) {
            return ['translate(',
              x(pe.dist(d)), ',',
              y(pe.z(d)) - 10, ')'
            ].join('');
          } else {
            return ['translate(',
              x(pe.dist(d)), ',',
              y(pe.z(d)) - 20, ') rotate(-60)'
            ].join('');
          }
        })
      .text(function(d) {
          return pe.sort(d) + (light ? '' : (' - ' + pe.title(d)));
        });

    poiEnter.append('line')
       .style('stroke', 'grey')
       .attr('x1', function(d) { return x(pe.dist(d));})
       .attr('y1', function(d) { return y(y.domain()[0]);})
       .attr('x2', function(d) { return x(pe.dist(d));})
       .attr('y2', function(d) { return y(pe.z(d));});

    poiEnter.selectAll('line')
       .style('shape-rendering', 'crispEdges');
  };

  function clearPois() {
    var g = svg.select('g');
    var ps = g.select('.pois');
    // remove any previously existing pois
    ps.selectAll('.poi').data([]).exit().remove();
  }

  return profile;
};
<commit_msg>Prevent having negative height and width in profile<commit_after>/**
 * @fileoverview Provides a D3js component to be used to draw an elevation
 * profile chart.
 *
 * Example usage:
 *
 * var selection = d3.select('#element_id');
 * var profile = ngeo.profile({
 *  elevationExtractor: {
 *    z: function (item) {return item['values']['z'];)},
 *    dist: function (item) {return item['dist'];)}
 *  },
 *  hoverCallback: function(point) {
 *    console.log(point.x, point.y);
 *  },
 *  outCallback: function() {
 *    console.log("out");
 *  }});
 * selection.datum(data).call(profile);
 *
 * The selection data must be an array.
 * The layout for the items of this array is unconstrained: the elevation
 * and distance values are extracted using the elevationExtractor config
 * option. Optionally, POIs can be displayed and depend on a poiExtractor
 * config option.
 *
 * The data below will work for the above example:
 * [
 *     {
 *         "y": 199340,
 *         "values": {"z": 788.7},
 *         "dist": 0.0,
 *         "x": 541620
 *     }, ...
 * ]
 */
goog.provide('ngeo.profile');

/**
 *
 * @return {Object}
 * @param {ngeox.profile.ProfileOptions} options
 * @export
 *
 */
ngeo.profile = function(options) {
  /**
   * @type {boolean}
   * Whether the simplified profile should be shown.
   */
  var light = goog.isDef(options.light) ? options.light : false;

  /**
   * The values for margins around the chart defined in pixels.
   */
  var margin = light ? {top: 0, right: 0, bottom: 0, left: 0} :
      {top: 10, right: 10, bottom: 30, left: 40};

  /**
   * Method to get the coordinate in pixels from a distance.
   */
  var bisectDistance = d3.bisector(function(d) {
    return elevationExtractor.dist(d);
  }).left;

  /**
   * Distance units. Either 'm' or 'km'.
   */
  var units;

  /**
   * Factor to determine whether to use 'm' or 'km'.
   */
  var xFactor;

  /**
   * @type {function(Object)}
   * Hover callback function.
   */
  var hoverCallback = goog.isDef(options.hoverCallback) ?
      options.hoverCallback : goog.nullFunction;

  /**
   * @type {function()}
   * Out callback function.
   */
  var outCallback = goog.isDef(options.outCallback) ?
      options.outCallback : goog.nullFunction;

  /**
   * The color to be used for filling the area.
   * Can be overriden using the '.area' CSS selector.
   * @type {string}
   */
  var fill_color = 'rgba(222, 222, 222, 0.5)';

  /**
   * The color to be used the line stroke.
   * Can be overriden using the '.line' CSS selector.
   * @type {string}
   */
  var stroke_color = '#F00';

  /**
   * @type {Object}
   */
  var svg;

  /**
   * D3 x scale.
   */
  var x;

  /**
   * D3 y scale.
   */
  var y;

  /**
   * Elevation data extractor used to get the dist and elevation values.
   */
  var elevationExtractor = options.elevationExtractor;

  /**
   * POI data extractor.
   */
  var poiExtractor = options.poiExtractor;

  var profile = function(selection) {
    selection.each(function(data) {
      var extractor = elevationExtractor;

      var width = Math.max(this.clientWidth - margin.right - margin.left, 0);
      x = d3.scale.linear().range([0, width]);

      var height = Math.max(this.clientHeight - margin.top - margin.bottom, 0);
      y = d3.scale.linear().range([height, 0]);

      var xAxis = d3.svg.axis().scale(x).orient('bottom');
      var yAxis = d3.svg.axis()
          .scale(y)
          .orient('left');

      var area = d3.svg.area()
          .x(function(d) { return x(extractor.dist(d)); })
          .y0(height)
          .y1(function(d) { return y(extractor.z(d)); });
      var line = d3.svg.line()
          .x(function(d) { return x(extractor.dist(d)); })
          .y(function(d) { return y(extractor.z(d)); });

      // Select the svg element, if it exists.
      svg = d3.select(this).selectAll('svg').data([data]);
      clearPois();

      // Otherwise, create the skeletal chart.
      var gEnter = svg.enter().append('svg').append('g');
      gEnter.style('font', '11px Arial');
      gEnter.append('path').attr('class', 'area')
          .style('fill', fill_color);
      gEnter.append('path').attr('class', 'line')
          .style('stroke', stroke_color)
          .style('fill', 'none');

      gEnter.insert('g', ':first-child')
          .attr('class', 'grid-y');

      if (!light) {
        gEnter.append('g')
          .attr('class', 'x axis')
          .attr('transform', 'translate(0,' + height + ')');

        gEnter.append('text')
          .attr('class', 'x label')
          .attr('text-anchor', 'end')
          .attr('x', width - 4)
          .attr('y', height - 4);

        gEnter.append('g')
          .attr('class', 'y axis');

        gEnter.append('text')
          .attr('class', 'y label')
          .attr('text-anchor', 'end')
          .attr('y', 6)
          .attr('dy', '.75em')
          .attr('transform', 'rotate(-90)')
          .style('fill', 'grey')
          .text('elevation (m)');

        gEnter.append('g')
          .attr('class', 'metas')
          .attr('transform', 'translate(' + (width + 3) + ', 0)');
      }

      gEnter.append('g').attr('class', 'pois');

      var yHover = gEnter.append('g').attr('class', 'y grid-hover');
      yHover.append('svg:line').attr('stroke-dasharray', '5,5');
      yHover.append('text');

      var xHover = gEnter.append('g').attr('class', 'x grid-hover');
      xHover.append('svg:line').attr('stroke-dasharray', '5,5');
      xHover.append('text');

      gEnter.append('rect')
          .attr('class', 'overlay')
          .attr('width', width)
          .attr('height', height)
          .style('fill', 'none')
          .style('pointer-events', 'all');

      // Update the outer dimensions.
      svg.attr('width', width + margin.left + margin.right)
          .attr('height', height + margin.top + margin.bottom);

      // Update the inner dimensions.
      var g = svg.select('g')
          .attr('transform', 'translate(' + margin.left + ',' +
              margin.top + ')');

      var xDomain = d3.extent(data, function(d) { return extractor.dist(d); });
      x.domain(xDomain);

      var yDomain = [d3.min(data, function(d) { return extractor.z(d); }),
            d3.max(data, function(d) { return extractor.z(d); })];

      var padding = (yDomain[1] - yDomain[0]) * 0.1;
      y.domain([yDomain[0] - padding, yDomain[1] + padding]);

      // Update the area path.
      g.select('.area')
          .transition()
          .attr('d', area);
      g.select('.line')
          .transition()
          .attr('d', line);

      if (xDomain[1] > 2000) {
        xFactor = 1000;
        units = 'km';
      } else {
        xFactor = 1;
        units = 'm';
      }

      if (!light) {
        xAxis.tickFormat(function(d) {
          return d / xFactor;
        });

        g.select('.x.axis')
          .transition()
          .call(xAxis);

        g.select('.x.label')
          .text('distance (' + units + ')')
          .style('fill', 'grey')
          .style('shape-rendering', 'crispEdges');

        g.select('.y.axis')
          .transition()
          .call(yAxis);
      }

      g.select('.grid-y')
          .transition()
          .call(yAxis.tickSize(-width, 0, 0).tickFormat(''))
          .selectAll('.tick line')
          .style('stroke', '#ccc')
          .style('opacity', 0.7);

      g.selectAll('.axis').selectAll('path, line')
          .style('fill', 'none')
          .style('stroke', '#000')
          .style('shape-rendering', 'crispEdges');

      g.selectAll('.grid-hover line')
          .style('stroke', '#222')
          .style('opacity', 0.8);

      g.select('.overlay')
          .on('mouseout', mouseout)
          .on('mousemove', mousemove);

      function mousemove() {
        var mouseX = d3.mouse(this)[0];
        var x0 = x.invert(mouseX);
        var i = bisectDistance(data, x0, 1);

        var point = data[i];
        var elevation = extractor.z(point);
        var dist = extractor.dist(point);

        g.select('.x.grid-hover')
            .style('display', 'inline')
            .select('line')
            .attr('x1', x(dist))
            .attr('y1', height)
            .attr('x2', x(dist))
            .attr('y2', y(elevation));

        g.select('.y.grid-hover')
            .style('display', 'inline')
            .select('line')
            .attr('x1', x(0))
            .attr('y1', y(elevation))
            .attr('x2', width)
            .attr('y2', y(elevation));

        var right = x0 > xDomain[1] / 2;
        var xtranslate = x(dist);
        xtranslate += right ? -10 : 10;

        g.select('.x.grid-hover text')
            .text(parseFloat(dist.toPrecision(3) / xFactor) + ' ' + units)
            .style('text-anchor', right ? 'end' : 'start')
            .attr('transform', 'translate(' + xtranslate + ',' +
                (height - 10) + ')');

        g.select('.y.grid-hover text')
            .text(Math.round(elevation) + ' m')
            .style('text-anchor', right ? 'end' : 'start')
            .attr('transform', 'translate(' + xtranslate + ',' +
                (y(elevation) - 10) + ')');
        hoverCallback.call(null, point);
      }

      function mouseout() {
        g.selectAll('.grid-hover')
            .style('display', 'none');
        outCallback.call(null);
      }
    });
  };

  profile.showPois = function(pois) {
    clearPois();
    if (!goog.isDef(pois)) {
      return;
    }

    var pe = poiExtractor;
    var g = svg.select('g');
    var profileData = svg.datum();
    var ps = g.select('.pois');

    var p = ps.selectAll('.poi').data(pois, function(d) {
      var i = bisectDistance(profileData, Math.round(pe.dist(d) * 10) / 10, 1);
      var point = profileData[i];
      if (point) {
        var z = elevationExtractor.z(point);
        pe.z(d, z);
      }
      return pe.id(d);
    });

    var poiEnter = p.enter()
      .append('g')
      .attr('class', 'poi');

    ps.selectAll('.poi')
      .style('opacity', 0)
      .transition()
      .duration(1000)
      .delay(100)
      .style('opacity', 1);

    poiEnter
      .append('text')
      .attr('x', light ? 0 : 9)
      .attr('dy', '.35em')
      .attr('text-anchor', light ? 'middle' : 'start')
      .attr('transform', function(d) {
          if (light) {
            return ['translate(',
              x(pe.dist(d)), ',',
              y(pe.z(d)) - 10, ')'
            ].join('');
          } else {
            return ['translate(',
              x(pe.dist(d)), ',',
              y(pe.z(d)) - 20, ') rotate(-60)'
            ].join('');
          }
        })
      .text(function(d) {
          return pe.sort(d) + (light ? '' : (' - ' + pe.title(d)));
        });

    poiEnter.append('line')
       .style('stroke', 'grey')
       .attr('x1', function(d) { return x(pe.dist(d));})
       .attr('y1', function(d) { return y(y.domain()[0]);})
       .attr('x2', function(d) { return x(pe.dist(d));})
       .attr('y2', function(d) { return y(pe.z(d));});

    poiEnter.selectAll('line')
       .style('shape-rendering', 'crispEdges');
  };

  function clearPois() {
    var g = svg.select('g');
    var ps = g.select('.pois');
    // remove any previously existing pois
    ps.selectAll('.poi').data([]).exit().remove();
  }

  return profile;
};
<|endoftext|>


<commit_before>'use strict';

var MIN_HEIGHT = 200;
var DATACAMP_LIGHT_URL = "https://light.datacamp.com/";

/**
 * Inspired by https://github.com/sindresorhus/strip-indent
 */
function stripIndent(code_block) {
	var match = code_block.match(/^[ \t]*(?=\S)/gm);

	if (!match) {
		return code_block;
	}

	var indent = Math.min.apply(Math, match.map(function (el) {
		return el.length;
	}));

	var re = new RegExp('^[ \\t]{' + indent + '}', 'gm');

	code_block = indent > 0 ? code_block.replace(re, '') : code_block;
	var lines = code_block.split(/\r?\n/);
	if (lines[lines.length-1].trim() === "") {
		lines.splice(lines.length-1, 1);
	}
	if (lines[0].trim() === "") {
		lines.splice(0, 1);
	}
	return lines.join("\n");
}

function unescapeHtml(safe) {
	return safe.replace(/&/g, '&')
		.replace(/</g, '<')
		.replace(/>/g, '>')
		.replace(/"/g, '"')
		.replace(/'/g, "'");
}

function processCodeTags(result_object, code_tags) {
	for (var i = 0; i < code_tags.length; i++) {
		if ("type" in code_tags[i].dataset) {
			var text = unescapeHtml(code_tags[i].innerHTML);
			var type = code_tags[i].dataset["type"];
			if (type === "pre-exercise-code") {
				result_object["pre-exercise-code"] = text;
			}
			else if (type === "sample-code") {
				result_object["sample-code"] = text;
			}
			else if (type === "solution") {
				result_object["solution"] = text;
			}
			else if (type === "sct") {
				result_object["sct"] = text;
			}
		}
	}
}

function processParagraphTags(result_object, paragraphs) {
	for (var i = 0; i < paragraphs.length; i++) {
		if ("type" in paragraphs[i].dataset) {
			var type = paragraphs[i].dataset["type"];
			if (type === "hint") {
				result_object["hint"] = paragraphs[i].innerHTML;
			}
		}
	}
}

function createURLData(data) {
	var result = [];
	for (var entry in data) {
		if (data.hasOwnProperty(entry))
			result.push(encodeURIComponent(entry) + "=" + encodeURIComponent(stripIndent(data[entry])));
	}
	return result.join("&");
}

function replaceDataCampExercises() {
	var exercises = document.querySelectorAll("[data-datacamp-exercise]");
	for (var i = 0; i < exercises.length; i++) {
		var exercise = exercises[i];

		if (!("lang" in exercise.dataset)) {
			console.log("DataCamp-Light: Missing the data-lang attribute.");
			return;
		}

		var result_object = {
			"language": exercise.dataset["lang"],
			"pre-exercise-code": "",
			"sample-code": "",
			"solution": "",
			"sct": "",
			"hint": "",
		}

		processParagraphTags(result_object, exercise.getElementsByTagName('p'));
		processCodeTags(result_object, exercise.getElementsByTagName('code'));

		// Actually replace
		while (exercise.lastChild) {
		    exercise.removeChild(exercise.lastChild);
		}

		// Add iframe
		var url = DATACAMP_LIGHT_URL + "?" + createURLData(result_object);
		var iframe = document.createElement("iframe");
		iframe.setAttribute("src", url);
		var style_attribute = "border: 1px solid #d5eaef;";

		// Calculate height of iframe
		var doHeightCalculation = true;
		var height;
		if ("height" in exercise.dataset) {
			var user_height = Math.round(exercise.dataset["height"]);
			if (!isNaN(user_height)) {
				if (user_height > MIN_HEIGHT) {
					height = user_height;
					doHeightCalculation = false;
				} else {
					console.log("The height attribute should be larger than " + MIN_HEIGHT + ".");
				}
			} else {
				console.log("Invalid height attribute.");
			}
		}

		if (doHeightCalculation) {
			// Get min-height
			var min_height = MIN_HEIGHT;
			if ("minHeight" in exercise.dataset) {
				var user_min_height = Math.round(exercise.dataset["minHeight"]);
				if (!isNaN(user_min_height)) {
					if (user_min_height > MIN_HEIGHT) {
						min_height = user_min_height;
					} else {
						console.log("The min-height attribute should be larger than " + MIN_HEIGHT + ".");
					}
				} else {
					console.log("Invalid min-height attribute.");
				}
			}

			// Get height based on sample-code
			var sample_lines_length = result_object["sample-code"].split(/\r?\n/).length;
			height = Math.max(
				47 + (sample_lines_length-2) * 18,
				min_height
			);

			if ("maxHeight" in exercise.dataset) {
				var user_max_height = Math.round(exercise.dataset["maxHeight"]);
				if (!isNaN(user_max_height)) {
					if (user_max_height > MIN_HEIGHT) {
						height = Math.min(height, user_max_height);
					} else {
						console.log("The max-height attribute should be larger than " + MIN_HEIGHT + ".");
					}
				} else {
					console.log("Invalid max-height attribute.");
				}
			}
		}

		iframe.style.height = height + "px";
		style_attribute += "height:" + height + "px;"

		// Set min-width
		iframe.style.minWidth = "600px";
		style_attribute += "min-width:600px;"

		// Set style attribute of iframe
		iframe.setAttribute("style", style_attribute);

		// Append iframe
		exercise.appendChild(iframe);
	}
}

document.addEventListener('DOMContentLoaded', replaceDataCampExercises, false);
<commit_msg>Put domain name in the datacamp light iframe url<commit_after>'use strict';

var MIN_HEIGHT = 200;
var DATACAMP_LIGHT_URL = "https://light.datacamp.com/";

/**
 * Inspired by https://github.com/sindresorhus/strip-indent
 */
function stripIndent(code_block) {
	var match = code_block.match(/^[ \t]*(?=\S)/gm);

	if (!match) {
		return code_block;
	}

	var indent = Math.min.apply(Math, match.map(function (el) {
		return el.length;
	}));

	var re = new RegExp('^[ \\t]{' + indent + '}', 'gm');

	code_block = indent > 0 ? code_block.replace(re, '') : code_block;
	var lines = code_block.split(/\r?\n/);
	if (lines[lines.length-1].trim() === "") {
		lines.splice(lines.length-1, 1);
	}
	if (lines[0].trim() === "") {
		lines.splice(0, 1);
	}
	return lines.join("\n");
}

function unescapeHtml(safe) {
	return safe.replace(/&/g, '&')
		.replace(/</g, '<')
		.replace(/>/g, '>')
		.replace(/"/g, '"')
		.replace(/'/g, "'");
}

function processCodeTags(result_object, code_tags) {
	for (var i = 0; i < code_tags.length; i++) {
		if ("type" in code_tags[i].dataset) {
			var text = unescapeHtml(code_tags[i].innerHTML);
			var type = code_tags[i].dataset["type"];
			if (type === "pre-exercise-code") {
				result_object["pre-exercise-code"] = text;
			}
			else if (type === "sample-code") {
				result_object["sample-code"] = text;
			}
			else if (type === "solution") {
				result_object["solution"] = text;
			}
			else if (type === "sct") {
				result_object["sct"] = text;
			}
		}
	}
}

function processParagraphTags(result_object, paragraphs) {
	for (var i = 0; i < paragraphs.length; i++) {
		if ("type" in paragraphs[i].dataset) {
			var type = paragraphs[i].dataset["type"];
			if (type === "hint") {
				result_object["hint"] = paragraphs[i].innerHTML;
			}
		}
	}
}

function createURLData(data) {
	var result = [];
	for (var entry in data) {
		if (data.hasOwnProperty(entry))
			result.push(encodeURIComponent(entry) + "=" + encodeURIComponent(stripIndent(data[entry])));
	}
	return result.join("&");
}

function replaceDataCampExercises() {
	var exercises = document.querySelectorAll("[data-datacamp-exercise]");
	for (var i = 0; i < exercises.length; i++) {
		var exercise = exercises[i];

		if (!("lang" in exercise.dataset)) {
			console.log("DataCamp-Light: Missing the data-lang attribute.");
			return;
		}

		var result_object = {
			"language": exercise.dataset["lang"],
			"pre-exercise-code": "",
			"sample-code": "",
			"solution": "",
			"sct": "",
			"hint": "",
		}

		processParagraphTags(result_object, exercise.getElementsByTagName('p'));
		processCodeTags(result_object, exercise.getElementsByTagName('code'));

		// Actually replace
		while (exercise.lastChild) {
		    exercise.removeChild(exercise.lastChild);
		}

		// Add iframe
		var url = DATACAMP_LIGHT_URL + (window.location.hostname ? "#/" + window.location.hostname + "/?" : "?") + createURLData(result_object);
		var iframe = document.createElement("iframe");
		iframe.setAttribute("src", url);
		var style_attribute = "border: 1px solid #d5eaef;";

		// Calculate height of iframe
		var doHeightCalculation = true;
		var height;
		if ("height" in exercise.dataset) {
			var user_height = Math.round(exercise.dataset["height"]);
			if (!isNaN(user_height)) {
				if (user_height > MIN_HEIGHT) {
					height = user_height;
					doHeightCalculation = false;
				} else {
					console.log("The height attribute should be larger than " + MIN_HEIGHT + ".");
				}
			} else {
				console.log("Invalid height attribute.");
			}
		}

		if (doHeightCalculation) {
			// Get min-height
			var min_height = MIN_HEIGHT;
			if ("minHeight" in exercise.dataset) {
				var user_min_height = Math.round(exercise.dataset["minHeight"]);
				if (!isNaN(user_min_height)) {
					if (user_min_height > MIN_HEIGHT) {
						min_height = user_min_height;
					} else {
						console.log("The min-height attribute should be larger than " + MIN_HEIGHT + ".");
					}
				} else {
					console.log("Invalid min-height attribute.");
				}
			}

			// Get height based on sample-code
			var sample_lines_length = result_object["sample-code"].split(/\r?\n/).length;
			height = Math.max(
				47 + (sample_lines_length-2) * 18,
				min_height
			);

			if ("maxHeight" in exercise.dataset) {
				var user_max_height = Math.round(exercise.dataset["maxHeight"]);
				if (!isNaN(user_max_height)) {
					if (user_max_height > MIN_HEIGHT) {
						height = Math.min(height, user_max_height);
					} else {
						console.log("The max-height attribute should be larger than " + MIN_HEIGHT + ".");
					}
				} else {
					console.log("Invalid max-height attribute.");
				}
			}
		}

		iframe.style.height = height + "px";
		style_attribute += "height:" + height + "px;"

		// Set min-width
		iframe.style.minWidth = "600px";
		style_attribute += "min-width:600px;"

		// Set style attribute of iframe
		iframe.setAttribute("style", style_attribute);

		// Append iframe
		exercise.appendChild(iframe);
	}
}

document.addEventListener('DOMContentLoaded', replaceDataCampExercises, false);
<|endoftext|>


<commit_before>'use strict'

const Config = require('../config.json')
const Fetcher = require('./Fetcher')
const GitHubAuth = require('./GitHubAuth')

const repoUrlPrefix = 'https://api.github.com/repos/'

function getTask(data) {
  const repoUrl = data.repository_url
  const repository = repoUrl.slice(repoUrlPrefix.length)
  const repositoryOwner = repository.split('/')[0]
  const type = typeof data.pull_request === 'object' ? 'pull' : 'issue'
  let apiUrl = data.url
  if (type === 'pull') {
    apiUrl = apiUrl.replace(/\/issues\//, '/pulls/')
  }
  return {
    storageKey: `${type}-${data.id}`,
    id: data.id,
    type,
    title: data.title,
    body: data.body,
    state: data.state,
    createdAt: data.created_at,
    updatedAt: data.updated_at,
    closedAt: data.closed_at,
    isPullRequest: type === 'pull',
    repositoryApiUrl: repoUrl,
    url: data.html_url,
    apiUrl,
    number: data.number,
    repository,
    repositoryOwner,
    repositoryOwnerUrl: `https://github.com/${repositoryOwner}`,
    repositoryOwnerAvatar: `https://github.com/${repositoryOwner}.png?size=30`,
    user: data.user.login,
    userUrl: data.user.html_url,
    userAvatar: `https://github.com/${data.user.login}.png?size=16`,
    userType: data.user.type,
  }
}

class GitHub extends Fetcher {
  constructor(token) {
    super()
    this.token = token
  }

  // https://developer.github.com/v3/activity/notifications/#list-your-notifications
  getNotifications() {
    const pastDate = new Date()
    pastDate.setDate(pastDate.getDate() - 31)
    const dateStr = pastDate.toISOString()
    return this.get(`notifications?since=${encodeURIComponent(dateStr)}`)
  }

  // https://developer.github.com/v3/search/#search-issues
  getTasks(query = Config.searchQuery) {
    const urlPath = `search/issues?q=${encodeURIComponent(query)}`
    return this.get(urlPath).then(({ items }) => items.map(d => getTask(d)))
  }

  // https://developer.github.com/v3/users/#get-the-authenticated-user
  getCurrentUser() {
    return this.get('user')
  }

  get(relativeOrAbsoluteUrl, previousJson) {
    let url = relativeOrAbsoluteUrl
    if (url.indexOf('http') !== 0) {
      url = `${Config.githubApiUrl}/${relativeOrAbsoluteUrl}`
    }
    if (!this.token) {
      this.token = GitHubAuth.getToken()
    }
    const opts = {
      headers: {
        Accept: 'application/vnd.github.v3+json',
        Authorization: `token ${this.token}`,
      },
    }
    return new Promise((resolve, reject) => super.get(url, opts).then(res => {
      const { json, headers } = res
      const combinedJson = this.combineJson(json, previousJson)
      const link = headers.get('Link')
      if (!link) {
        return resolve(combinedJson)
      }
      const nextUrl = this.getNextUrl(link)
      if (nextUrl) {
        return this.get(nextUrl, combinedJson).then(resolve).catch(reject)
      }
      return resolve(combinedJson)
    }).catch(reject))
  }

  combineJson(json1, json2) {
    if (typeof json2 === 'undefined') {
      return json1
    }
    const is1Array = json1.constructor.name === 'Array'
    const is2Array = json2.constructor.name === 'Array'
    if (is1Array && is2Array) {
      return json1.concat(json2)
    }
    return Object.assign({}, json1, json2)
  }

  // Sample input:
  // Link: <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=15>; rel="next",
  // <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last",
  // <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=1>; rel="first",
  // <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=13>; rel="prev"
  //
  // Sample output:
  // https://api.github.com/search/code?q=addClass+user%3Amozilla&page=15
  getNextUrl(link) {
    const urlsAndRels = link.split(',')
    let nextUrl
    urlsAndRels.forEach(str => {
      const urlAndRel = str.trim().split('; ')
      if (urlAndRel[1] === 'rel="next"') {
        const urlInBrackets = urlAndRel[0]
        nextUrl = urlInBrackets.slice(1, urlInBrackets.length - 1)
        return
      }
    })
    return nextUrl
  }
}

module.exports = GitHub
<commit_msg>Add markAsRead to GitHub for notifications<commit_after>'use strict'

const Config = require('../config.json')
const Fetcher = require('./Fetcher')
const GitHubAuth = require('./GitHubAuth')

const repoUrlPrefix = 'https://api.github.com/repos/'

function getTask(data) {
  const repoUrl = data.repository_url
  const repository = repoUrl.slice(repoUrlPrefix.length)
  const repositoryOwner = repository.split('/')[0]
  const type = typeof data.pull_request === 'object' ? 'pull' : 'issue'
  let apiUrl = data.url
  if (type === 'pull') {
    apiUrl = apiUrl.replace(/\/issues\//, '/pulls/')
  }
  return {
    storageKey: `${type}-${data.id}`,
    id: data.id,
    type,
    title: data.title,
    body: data.body,
    state: data.state,
    createdAt: data.created_at,
    updatedAt: data.updated_at,
    closedAt: data.closed_at,
    isPullRequest: type === 'pull',
    repositoryApiUrl: repoUrl,
    url: data.html_url,
    apiUrl,
    number: data.number,
    repository,
    repositoryOwner,
    repositoryOwnerUrl: `https://github.com/${repositoryOwner}`,
    repositoryOwnerAvatar: `https://github.com/${repositoryOwner}.png?size=30`,
    user: data.user.login,
    userUrl: data.user.html_url,
    userAvatar: `https://github.com/${data.user.login}.png?size=16`,
    userType: data.user.type,
  }
}

class GitHub extends Fetcher {
  constructor(token) {
    super()
    this.token = token
  }

  // https://developer.github.com/v3/activity/notifications/#list-your-notifications
  getNotifications() {
    const pastDate = new Date()
    pastDate.setDate(pastDate.getDate() - 31)
    const dateStr = pastDate.toISOString()
    return this.get(`notifications?since=${encodeURIComponent(dateStr)}`)
  }

  // https://developer.github.com/v3/search/#search-issues
  getTasks(query = Config.searchQuery) {
    const urlPath = `search/issues?q=${encodeURIComponent(query)}`
    return this.get(urlPath).then(({ items }) => items.map(d => getTask(d)))
  }

  // https://developer.github.com/v3/users/#get-the-authenticated-user
  getCurrentUser() {
    return this.get('user')
  }

  // https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read
  markAsRead(url) {
    return this.patch(url, { ignoreBody: true })
  }

  patch(relativeOrAbsoluteUrl, opts) {
    let url = relativeOrAbsoluteUrl
    if (url.indexOf('http') !== 0) {
      url = `${Config.githubApiUrl}/${relativeOrAbsoluteUrl}`
    }
    const options = opts || {}
    options.headers = this.getHeaders()
    return super.patch(url, options)
  }

  getHeaders() {
    if (!this.token) {
      this.token = GitHubAuth.getToken()
    }
    return {
      Accept: 'application/vnd.github.v3+json',
      Authorization: `token ${this.token}`,
    }
  }

  get(relativeOrAbsoluteUrl, previousJson) {
    let url = relativeOrAbsoluteUrl
    if (url.indexOf('http') !== 0) {
      url = `${Config.githubApiUrl}/${relativeOrAbsoluteUrl}`
    }
    const opts = { headers: this.getHeaders() }
    return new Promise((resolve, reject) => super.get(url, opts).then(res => {
      const { json, headers } = res
      const combinedJson = this.combineJson(json, previousJson)
      const link = headers.get('Link')
      if (!link) {
        return resolve(combinedJson)
      }
      const nextUrl = this.getNextUrl(link)
      if (nextUrl) {
        return this.get(nextUrl, combinedJson).then(resolve).catch(reject)
      }
      return resolve(combinedJson)
    }).catch(reject))
  }

  combineJson(json1, json2) {
    if (typeof json2 === 'undefined') {
      return json1
    }
    const is1Array = json1.constructor.name === 'Array'
    const is2Array = json2.constructor.name === 'Array'
    if (is1Array && is2Array) {
      return json1.concat(json2)
    }
    return Object.assign({}, json1, json2)
  }

  // Sample input:
  // Link: <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=15>; rel="next",
  // <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last",
  // <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=1>; rel="first",
  // <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=13>; rel="prev"
  //
  // Sample output:
  // https://api.github.com/search/code?q=addClass+user%3Amozilla&page=15
  getNextUrl(link) {
    const urlsAndRels = link.split(',')
    let nextUrl
    urlsAndRels.forEach(str => {
      const urlAndRel = str.trim().split('; ')
      if (urlAndRel[1] === 'rel="next"') {
        const urlInBrackets = urlAndRel[0]
        nextUrl = urlInBrackets.slice(1, urlInBrackets.length - 1)
        return
      }
    })
    return nextUrl
  }
}

module.exports = GitHub
<|endoftext|>


<commit_before>/*
 * @flow
 */

import Immutable from 'immutable';

import FullyQualifiedName from './FullyQualifiedName';

import Logger from '../utils/Logger';

import {
  isNotDefined,
  isNonEmptyArray
} from '../utils/LangUtils';

const LOG = new Logger('Schema');

/**
 * @class Schema
 * @memberof loom-data
 */
export default class Schema {

  fqn :FullyQualifiedName;
  entityTypes :EntityType[];
  propertyTypes :PropertyType[];

  constructor(
      fqn :FullyQualifiedName,
      entityTypes :EntityType[],
      propertyTypes :PropertyType[]) {

    this.fqn = fqn;
    this.entityTypes = entityTypes;
    this.propertyTypes = propertyTypes;
  }
}

/**
 * @class SchemaBuilder
 * @memberof loom-data
 */
export class SchemaBuilder {

  fqn :FullyQualifiedName;
  entityTypes :List<EntityType>;
  propertyTypes :List<PropertyType>;

  setFullyQualifiedName(fqn :FullyQualifiedName) :SchemaBuilder {

    if (!FullyQualifiedName.isValidFqn(fqn.getFullyQualifiedName())) {
      throw new Error('invalid parameter: fqn must be a valid FQN');
    }

    this.fqn = fqn;
    return this;
  }

  setEntityTypes(entityTypes :EntityType[]) :SchemaBuilder {

    if (!isNonEmptyArray(entityTypes)) {
      throw new Error('invalid parameter: entityTypes must be a non-empty array');
    }

    let errorMessage = '';
    const allValid = entityTypes.reduce((valid, entityType, index) => {
      if (!valid) {
        return false;
      }
      if (!EntityType.isValid(entityType)) {
        errorMessage = `invalid parameter: entityTypes[${index}] must be a valid EntityType`;
        return false;
      }
      return valid;
    }, true);

    if (!allValid) {
      throw new Error(errorMessage);
    }

    this.entityTypes = Immutable.List().withMutations((list :List<EntityType>) => {
      entityTypes.forEach((entityType :EntityType) => {
        list.push(entityType);
      });
    });

    return this;
  }

  setPropertyTypes(propertyTypes :PropertyType[]) :SchemaBuilder {

    if (!isNonEmptyArray(propertyTypes)) {
      throw new Error('invalid parameter: propertyTypes must be a non-empty array');
    }

    let errorMessage = '';
    const allValid = propertyTypes.reduce((valid, propertyType, index) => {
      if (!valid) {
        return false;
      }
      if (!PropertyType.isValid(propertyType)) {
        errorMessage = `invalid parameter: propertyTypes[${index}] must be a valid PropertyType`;
        return false;
      }
      return valid;
    }, true);

    if (!allValid) {
      throw new Error(errorMessage);
    }

    this.propertyTypes = Immutable.List().withMutations((list :List<PropertyType>) => {
      propertyTypes.forEach((propertyType :PropertyType) => {
        list.push(propertyType);
      });
    });

    return this;
  }

  build() :Schema {

    return new Schema(
      this.fqn,
      this.entityTypes,
      this.propertyTypes
    );
  }
}

export function isValid(schema :any) :boolean {

  if (isNotDefined(schema)) {

    LOG.error('invalid parameter: schema must be defined', schema);
    return false;
  }

  try {

    (new SchemaBuilder())
      .setFullyQualifiedName(schema.fqn)
      .setEntityTypes(schema.entityTypes)
      .setPropertyTypes(schema.propertyTypes)
      .build();

    return true;
  }
  catch (e) {

    LOG.error(e, schema);
    return false;
  }
}
<commit_msg>need to actually import EntityType and PropertyType<commit_after>/*
 * @flow
 */

import Immutable from 'immutable';

import FullyQualifiedName from './FullyQualifiedName';

import Logger from '../utils/Logger';

import * as EntityType from './EntityType';
import * as PropertyType from './PropertyType';

import {
  isNotDefined,
  isNonEmptyArray
} from '../utils/LangUtils';

const LOG = new Logger('Schema');

/**
 * @class Schema
 * @memberof loom-data
 */
export default class Schema {

  fqn :FullyQualifiedName;
  entityTypes :EntityType[];
  propertyTypes :PropertyType[];

  constructor(
      fqn :FullyQualifiedName,
      entityTypes :EntityType[],
      propertyTypes :PropertyType[]) {

    this.fqn = fqn;
    this.entityTypes = entityTypes;
    this.propertyTypes = propertyTypes;
  }
}

/**
 * @class SchemaBuilder
 * @memberof loom-data
 */
export class SchemaBuilder {

  fqn :FullyQualifiedName;
  entityTypes :List<EntityType>;
  propertyTypes :List<PropertyType>;

  setFullyQualifiedName(fqn :FullyQualifiedName) :SchemaBuilder {

    if (!FullyQualifiedName.isValidFqn(fqn.getFullyQualifiedName())) {
      throw new Error('invalid parameter: fqn must be a valid FQN');
    }

    this.fqn = fqn;
    return this;
  }

  setEntityTypes(entityTypes :EntityType[]) :SchemaBuilder {

    if (!isNonEmptyArray(entityTypes)) {
      throw new Error('invalid parameter: entityTypes must be a non-empty array');
    }

    let errorMessage = '';
    const allValid = entityTypes.reduce((valid, entityType, index) => {
      if (!valid) {
        return false;
      }
      if (!EntityType.isValid(entityType)) {
        errorMessage = `invalid parameter: entityTypes[${index}] must be a valid EntityType`;
        return false;
      }
      return valid;
    }, true);

    if (!allValid) {
      throw new Error(errorMessage);
    }

    this.entityTypes = Immutable.List().withMutations((list :List<EntityType>) => {
      entityTypes.forEach((entityType :EntityType) => {
        list.push(entityType);
      });
    });

    return this;
  }

  setPropertyTypes(propertyTypes :PropertyType[]) :SchemaBuilder {

    if (!isNonEmptyArray(propertyTypes)) {
      throw new Error('invalid parameter: propertyTypes must be a non-empty array');
    }

    let errorMessage = '';
    const allValid = propertyTypes.reduce((valid, propertyType, index) => {
      if (!valid) {
        return false;
      }
      if (!PropertyType.isValid(propertyType)) {
        errorMessage = `invalid parameter: propertyTypes[${index}] must be a valid PropertyType`;
        return false;
      }
      return valid;
    }, true);

    if (!allValid) {
      throw new Error(errorMessage);
    }

    this.propertyTypes = Immutable.List().withMutations((list :List<PropertyType>) => {
      propertyTypes.forEach((propertyType :PropertyType) => {
        list.push(propertyType);
      });
    });

    return this;
  }

  build() :Schema {

    return new Schema(
      this.fqn,
      this.entityTypes,
      this.propertyTypes
    );
  }
}

export function isValid(schema :any) :boolean {

  if (isNotDefined(schema)) {

    LOG.error('invalid parameter: schema must be defined', schema);
    return false;
  }

  try {

    (new SchemaBuilder())
      .setFullyQualifiedName(schema.fqn)
      .setEntityTypes(schema.entityTypes)
      .setPropertyTypes(schema.propertyTypes)
      .build();

    return true;
  }
  catch (e) {

    LOG.error(e, schema);
    return false;
  }
}
<|endoftext|>


<commit_before>"use strict";

let Command = require("./commands");
let mongoose = require("mongoose");
let util = require("util");

let PollSchema = new mongoose.Schema({
  question: String,
  user: {
    _id: false,
    nick: String,
    account: String,
  },
  options: [String],
  created: {
    type: Date,
    default: Date.now,
  },
  votes: [{
    _id: false,
    value: String,
    user: {
      _id: false,
      nick: String,
      account: String,
    }
  }]
});

PollSchema.methods.isAnswerValid = function(value) {
  return this.options.indexOf(value) != -1;
};

PollSchema.methods.vote = function(nick, value) {
  // if (!account || typeof account != "object") {
  //   throw new Error("Invalid user data");
  // }
  if (!this.isAnswerValid(value)) {
    throw new Error("Invalid answer");
  }

  for (let i = 0; i < this.votes.length; i++) {
    let vote = this.votes[i];
    if (vote.user.nick == nick) {
      vote.value = value;
      return;
    }
  }

  this.votes.push({value: value, user: {nick: nick}});
}

/**
 * NOTE: Currently does not allow concurrent polls on different channels.
 */
class PollManager {
  constructor(whois) {
    this.whois = whois;
    this.poll = null;
    this.timer = null;
  }

  start(poll, expires) {
    return new Promise((resolve, reject) => {
      if (this.poll) {
        return reject(new Error("Cannot start a new poll while another one is active"));
      }
      this.poll = poll;
      this.timer = setTimeout(() => {
        resolve(new Stats(poll));
        this.finish();
      }, (expires || 60) * 1000);
    });
  }

  vote(nick, value) {
    if (!this.poll) {
      throw new Error("There is no active poll");
    }
    this.poll.vote(nick, value);
  }

  finish() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.poll.save();
      this.poll = null;
      this.timer = null;
    }
  }
}

class Stats {
  constructor(poll) {
    this.poll = poll;
  }

  toString() {
    let cache = new Map(this.poll.options.map(option => [option.toLowerCase(), option]));
    let votes = new Map(this.poll.options.map(option => [option, 0]));

    this.votes.forEach(vote => {
      let key = cache.get(vote.value.toLowerCase());
      votes.set(key, votes.get(key) + 1);
    });

    let output = [...votes.entries()]
      .sort((a, b) => b[1] - a[1])
      .map(item => item.join(": "))
      .join(", ");
    return output;
  }

  get votes() {
    return this.poll.votes;
  }
}

exports.configure = services => {
  let commands = services.get("command.manager");
  let database = services.get("database");
  let Poll = database.model("poll", PollSchema);

  services.registerFactory("poll.manager", () => new PollManager);

  commands.add("poll", Command.ALLOW_WHITELIST, command => {
    let manager = services.get("poll.manager");

    let config = new Map(command.params.filter(p => p.indexOf("=") != -1).map(p => p.split("=")));
    let question = command.params.slice(config.size).join(" ").replace(/\??$/, "?");
    let options = (config.get("o") || "yes,no").split(",");
    let poll = new Poll({question: question, options: options, user: {nick: command.nick}});

    options.forEach(cmd => commands.add(cmd, command => manager.vote(command.nick, cmd)));
    command.send(util.format("New poll: %s (%s)", poll.question, options.map(o => "!" + o).join(", ")));

    return manager.start(poll, config.get("t")).then(stats => {
      options.forEach(cmd => commands.delete(cmd));
      return ["Poll finished!", poll.question + " " + stats.toString()];
    });
  });
};
<commit_msg>Make poll labels all-caps to make them stand out<commit_after>"use strict";

let Command = require("./commands");
let mongoose = require("mongoose");
let util = require("util");

let PollSchema = new mongoose.Schema({
  question: String,
  user: {
    _id: false,
    nick: String,
    account: String,
  },
  options: [String],
  created: {
    type: Date,
    default: Date.now,
  },
  votes: [{
    _id: false,
    value: String,
    user: {
      _id: false,
      nick: String,
      account: String,
    }
  }]
});

PollSchema.methods.isAnswerValid = function(value) {
  return this.options.indexOf(value) != -1;
};

PollSchema.methods.vote = function(nick, value) {
  // if (!account || typeof account != "object") {
  //   throw new Error("Invalid user data");
  // }
  if (!this.isAnswerValid(value)) {
    throw new Error("Invalid answer");
  }

  for (let i = 0; i < this.votes.length; i++) {
    let vote = this.votes[i];
    if (vote.user.nick == nick) {
      vote.value = value;
      return;
    }
  }

  this.votes.push({value: value, user: {nick: nick}});
}

/**
 * NOTE: Currently does not allow concurrent polls on different channels.
 */
class PollManager {
  constructor(whois) {
    this.whois = whois;
    this.poll = null;
    this.timer = null;
  }

  start(poll, expires) {
    return new Promise((resolve, reject) => {
      if (this.poll) {
        return reject(new Error("Cannot start a new poll while another one is active"));
      }
      this.poll = poll;
      this.timer = setTimeout(() => {
        resolve(new Stats(poll));
        this.finish();
      }, (expires || 60) * 1000);
    });
  }

  vote(nick, value) {
    if (!this.poll) {
      throw new Error("There is no active poll");
    }
    this.poll.vote(nick, value);
  }

  finish() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.poll.save();
      this.poll = null;
      this.timer = null;
    }
  }
}

class Stats {
  constructor(poll) {
    this.poll = poll;
  }

  toString() {
    let cache = new Map(this.poll.options.map(option => [option.toLowerCase(), option]));
    let votes = new Map(this.poll.options.map(option => [option, 0]));

    this.votes.forEach(vote => {
      let key = cache.get(vote.value.toLowerCase());
      votes.set(key, votes.get(key) + 1);
    });

    let output = [...votes.entries()]
      .sort((a, b) => b[1] - a[1])
      .map(item => item.join(": "))
      .join(", ");
    return output;
  }

  get votes() {
    return this.poll.votes;
  }
}

exports.configure = services => {
  let commands = services.get("command.manager");
  let database = services.get("database");
  let Poll = database.model("poll", PollSchema);

  services.registerFactory("poll.manager", () => new PollManager);

  commands.add("poll", Command.ALLOW_WHITELIST, command => {
    let manager = services.get("poll.manager");

    let config = new Map(command.params.filter(p => p.indexOf("=") != -1).map(p => p.split("=")));
    let question = command.params.slice(config.size).join(" ").replace(/\??$/, "?");
    let options = (config.get("o") || "yes,no").split(",");
    let poll = new Poll({question: question, options: options, user: {nick: command.nick}});

    options.forEach(cmd => commands.add(cmd, command => manager.vote(command.nick, cmd)));
    command.send(util.format("NEW POLL: %s (%s)", poll.question, options.map(o => "!" + o).join(", ")));

    return manager.start(poll, config.get("t")).then(stats => {
      options.forEach(cmd => commands.delete(cmd));
      return ["POLL FINISHED!", poll.question + " " + stats.toString()];
    });
  });
};
<|endoftext|>


<commit_before>( function() {

  var MomentPickerFactory = function(Backbone, moment) {

    var weekdays = moment()._lang._weekdaysMin;
    var tmplHead = '
' +
      '
' +
      '	' +
      '<%= month %> <%= year %>	' +
      '	' +
      '
' +
      '
' +
      '' + weekdays[0] + '	' +
      '' + weekdays[1] + '	' +
      '' + weekdays[2] + '	' +
      '' + weekdays[3] + '	' +
      '' + weekdays[4] + '	' +
      '' + weekdays[5] + '	' +
      '' + weekdays[6] + '	' +
      '
' +
      '
';

    var tmplRow = '
' +
      '<td class="day day-0 <%= week.day0.date ? "clickable" : "empty" %><%= week.day0.full == selected ? " selected": "" %>" data-date="<%= week.day0.full %>"><%= week.day0.date %>	' +
      '<td class="day day-1 <%= week.day1.date ? "clickable" : "empty" %><%= week.day1.full == selected ? " selected": "" %>" data-date="<%= week.day1.full %>"><%= week.day1.date %>	' +
      '<td class="day day-2 <%= week.day2.date ? "clickable" : "empty" %><%= week.day2.full == selected ? " selected": "" %>" data-date="<%= week.day2.full %>"><%= week.day2.date %>	' +
      '<td class="day day-3 <%= week.day3.date ? "clickable" : "empty" %><%= week.day3.full == selected ? " selected": "" %>" data-date="<%= week.day3.full %>"><%= week.day3.date %>	' +
      '<td class="day day-4 <%= week.day4.date ? "clickable" : "empty" %><%= week.day4.full == selected ? " selected": "" %>" data-date="<%= week.day4.full %>"><%= week.day4.date %>	' +
      '<td class="day day-5 <%= week.day5.date ? "clickable" : "empty" %><%= week.day5.full == selected ? " selected": "" %>" data-date="<%= week.day5.full %>"><%= week.day5.date %>	' +
      '<td class="day day-6 <%= week.day6.date ? "clickable" : "empty" %><%= week.day6.full == selected ? " selected": "" %>" data-date="<%= week.day6.full %>"><%= week.day6.date %>	' +
      '
';

    var MomentPicker = Backbone.View.extend({
      _tmplHead:    tmplHead,
      _tmplRow:     tmplRow,
      tagName:      'table',
      className:    'datepicker',

      constructor: function(options) {
        Backbone.View.prototype.constructor.call(this, options);

        this._initialize(options);
      },

      /**
       * Private initializer
       */
      _initialize: function(options) {
        if (options && _.has(options, 'date')) {
          this.updateSelectedMoment(options.date);
        } else {
          this.updateSelectedMoment(moment().startOf('day'));
        }
      },

      render: function() {
        this.$el.html(_.template(this._tmplHead, {
          month: moment((this._activeMoment.month()+1).toString(), 'M').format('MMM'),
          year: this._activeMoment.format("YYYY")
        }));

        var self = this;
        _.each(this.getActiveWeeks(this._activeMoment), function(week) {
          self.$el.append(_.template(self._tmplRow, {
            week: week,
            selected: self._selectedMoment.format('YYYY-MM-DD')
          }));
        });

        return this;
      },

      getActiveWeeks: function(date) {
        var daysInActiveMonth = _.range(1, date.clone().date(1).daysInMonth() + 1); // daysInMonth is buggy in current Moment version and returns wrong number when using end of certain months (e.g. May 31, 2012). TODO: reeval when fixed upstream

        var y = date.year();
        var m = date.month();
        var weeksInActiveMonth = _.chain(daysInActiveMonth)
          .map(function(d) { // group by the week of the year
            var mom = moment([y, m, d]);
            return {
              date: d,
              day: mom.day(),
              yearweek: mom.format('YYYY') + '-' + mom.format('w'), // year-week to deal with january
              month: m,
              year: y,
              full: mom.format('YYYY-MM-DD')
            };
          })
          .groupBy('yearweek')
          .map(function(w) { // rekey each date in the week to day
            var reKeyed = {};
            // create an empty object with a dayX property for each day of the week
            _.each(_.range(7), function(d) {
              reKeyed['day' + d] = {};
            });
            _.each(w, function(d) {
              reKeyed['day' + d.day] = d;
            });

            return reKeyed;
          })
          .value();

        return weeksInActiveMonth;
      },

      events: {
        'click td.day.clickable': 'clickDay',
        'click th.prev-month': 'prevMonth',
        'click th.next-month': 'nextMonth'
      },

      clickDay: function(e) {
        e.preventDefault();

        this._setSelectedMoment(moment($(e.target).data('date')));
      },

      prevMonth: function(e) {
        this._activeMoment.subtract('months', 1);
        this.render();
      },

      nextMonth: function(e) {
        this._activeMoment.add('months', 1);
        this.render();
      },

      _setSelectedMoment: function(mom, silent) {
        silent = ( silent !== undefined ) ? silent : false; // default silent false

        this._selectedMoment = mom;
        this.$('td.day.selected').removeClass('selected');
        this.$('td.day[data-date="' + mom.format('YYYY-MM-DD') + '"]').addClass('selected');
        if (!silent)
          this.trigger('selected', { moment: mom, date: mom.format('MMM D, YYYY')});
      },

      updateSelectedMoment: function(date, silent) {
        var mom = moment(date);
        if (!isNaN(mom._d.getTime())) { // check for valid date. TODO: Use upcoming moment.js isValid function
          mom.startOf('day');
          this._setSelectedMoment(mom, silent);
          this._activeMoment = mom.clone();
          this.render();
          return mom;
        } else {
          return false;
        }
      },

      getSelectedMoment: function() {
        return this._selectedMoment;
      }

    });

    return MomentPicker;

  };

  if (typeof define === 'function' && define.amd) {
    define(['backbone', 'moment'], function(Backbone, moment) {
      return MomentPickerFactory(Backbone, moment);
    });
  } else {
    // attach to global object
    this.MomentPicker = MomentPickerFactory(Backbone, moment);
  }

}).call(this);
<commit_msg>Update navigation icons for Font Awesome 4<commit_after>( function() {

  var MomentPickerFactory = function(Backbone, moment) {

    var weekdays = moment()._lang._weekdaysMin;
    var tmplHead = '
' +
      '
' +
      '	' +
      '<%= month %> <%= year %>	' +
      '	' +
      '
' +
      '
' +
      '' + weekdays[0] + '	' +
      '' + weekdays[1] + '	' +
      '' + weekdays[2] + '	' +
      '' + weekdays[3] + '	' +
      '' + weekdays[4] + '	' +
      '' + weekdays[5] + '	' +
      '' + weekdays[6] + '	' +
      '
' +
      '
';

    var tmplRow = '
' +
      '<td class="day day-0 <%= week.day0.date ? "clickable" : "empty" %><%= week.day0.full == selected ? " selected": "" %>" data-date="<%= week.day0.full %>"><%= week.day0.date %>	' +
      '<td class="day day-1 <%= week.day1.date ? "clickable" : "empty" %><%= week.day1.full == selected ? " selected": "" %>" data-date="<%= week.day1.full %>"><%= week.day1.date %>	' +
      '<td class="day day-2 <%= week.day2.date ? "clickable" : "empty" %><%= week.day2.full == selected ? " selected": "" %>" data-date="<%= week.day2.full %>"><%= week.day2.date %>	' +
      '<td class="day day-3 <%= week.day3.date ? "clickable" : "empty" %><%= week.day3.full == selected ? " selected": "" %>" data-date="<%= week.day3.full %>"><%= week.day3.date %>	' +
      '<td class="day day-4 <%= week.day4.date ? "clickable" : "empty" %><%= week.day4.full == selected ? " selected": "" %>" data-date="<%= week.day4.full %>"><%= week.day4.date %>	' +
      '<td class="day day-5 <%= week.day5.date ? "clickable" : "empty" %><%= week.day5.full == selected ? " selected": "" %>" data-date="<%= week.day5.full %>"><%= week.day5.date %>	' +
      '<td class="day day-6 <%= week.day6.date ? "clickable" : "empty" %><%= week.day6.full == selected ? " selected": "" %>" data-date="<%= week.day6.full %>"><%= week.day6.date %>	' +
      '
';

    var MomentPicker = Backbone.View.extend({
      _tmplHead:    tmplHead,
      _tmplRow:     tmplRow,
      tagName:      'table',
      className:    'datepicker',

      constructor: function(options) {
        Backbone.View.prototype.constructor.call(this, options);

        this._initialize(options);
      },

      /**
       * Private initializer
       */
      _initialize: function(options) {
        if (options && _.has(options, 'date')) {
          this.updateSelectedMoment(options.date);
        } else {
          this.updateSelectedMoment(moment().startOf('day'));
        }
      },

      render: function() {
        this.$el.html(_.template(this._tmplHead, {
          month: moment((this._activeMoment.month()+1).toString(), 'M').format('MMM'),
          year: this._activeMoment.format("YYYY")
        }));

        var self = this;
        _.each(this.getActiveWeeks(this._activeMoment), function(week) {
          self.$el.append(_.template(self._tmplRow, {
            week: week,
            selected: self._selectedMoment.format('YYYY-MM-DD')
          }));
        });

        return this;
      },

      getActiveWeeks: function(date) {
        var daysInActiveMonth = _.range(1, date.clone().date(1).daysInMonth() + 1); // daysInMonth is buggy in current Moment version and returns wrong number when using end of certain months (e.g. May 31, 2012). TODO: reeval when fixed upstream

        var y = date.year();
        var m = date.month();
        var weeksInActiveMonth = _.chain(daysInActiveMonth)
          .map(function(d) { // group by the week of the year
            var mom = moment([y, m, d]);
            return {
              date: d,
              day: mom.day(),
              yearweek: mom.format('YYYY') + '-' + mom.format('w'), // year-week to deal with january
              month: m,
              year: y,
              full: mom.format('YYYY-MM-DD')
            };
          })
          .groupBy('yearweek')
          .map(function(w) { // rekey each date in the week to day
            var reKeyed = {};
            // create an empty object with a dayX property for each day of the week
            _.each(_.range(7), function(d) {
              reKeyed['day' + d] = {};
            });
            _.each(w, function(d) {
              reKeyed['day' + d.day] = d;
            });

            return reKeyed;
          })
          .value();

        return weeksInActiveMonth;
      },

      events: {
        'click td.day.clickable': 'clickDay',
        'click th.prev-month': 'prevMonth',
        'click th.next-month': 'nextMonth'
      },

      clickDay: function(e) {
        e.preventDefault();

        this._setSelectedMoment(moment($(e.target).data('date')));
      },

      prevMonth: function(e) {
        this._activeMoment.subtract('months', 1);
        this.render();
      },

      nextMonth: function(e) {
        this._activeMoment.add('months', 1);
        this.render();
      },

      _setSelectedMoment: function(mom, silent) {
        silent = ( silent !== undefined ) ? silent : false; // default silent false

        this._selectedMoment = mom;
        this.$('td.day.selected').removeClass('selected');
        this.$('td.day[data-date="' + mom.format('YYYY-MM-DD') + '"]').addClass('selected');
        if (!silent)
          this.trigger('selected', { moment: mom, date: mom.format('MMM D, YYYY')});
      },

      updateSelectedMoment: function(date, silent) {
        var mom = moment(date);
        if (!isNaN(mom._d.getTime())) { // check for valid date. TODO: Use upcoming moment.js isValid function
          mom.startOf('day');
          this._setSelectedMoment(mom, silent);
          this._activeMoment = mom.clone();
          this.render();
          return mom;
        } else {
          return false;
        }
      },

      getSelectedMoment: function() {
        return this._selectedMoment;
      }

    });

    return MomentPicker;

  };

  if (typeof define === 'function' && define.amd) {
    define(['backbone', 'moment'], function(Backbone, moment) {
      return MomentPickerFactory(Backbone, moment);
    });
  } else {
    // attach to global object
    this.MomentPicker = MomentPickerFactory(Backbone, moment);
  }

}).call(this);
<|endoftext|>


<commit_before>import { bindable, customElement } from 'aurelia-templating';
import { bindingMode } from 'aurelia-binding';
import { inject } from 'aurelia-dependency-injection';
import { getBooleanFromAttributeValue } from '../common/attributes';
import { AttributeManager } from '../common/attributeManager';

@customElement('md-navbar')
@inject(Element)
export class MdNavbar {
  @bindable({
    defaultBindingMode: bindingMode.oneTime
  }) fixed;
  fixedAttributeManager;

  constructor(element) {
    this.element = element;
  }

  attached() {
    this.fixedAttributeManager = new AttributeManager(this.fixedAnchor);
    if (getBooleanFromAttributeValue(this.fixed)) {
      this.fixedAttributeManager.addClasses('navbar-fixed');
    }
  }

  detached() {
    if (getBooleanFromAttributeValue(this.fixed)) {
      this.fixedAttributeManager.removeClasses('navbar-fixed');
    }
  }
}
<commit_msg>refactor(navbar): rename fixed attribute to md-fixed<commit_after>import { bindable, customElement } from 'aurelia-templating';
import { bindingMode } from 'aurelia-binding';
import { inject } from 'aurelia-dependency-injection';
import { getBooleanFromAttributeValue } from '../common/attributes';
import { AttributeManager } from '../common/attributeManager';

@customElement('md-navbar')
@inject(Element)
export class MdNavbar {
  @bindable({
    defaultBindingMode: bindingMode.oneTime
  }) mdFixed;
  fixedAttributeManager;

  constructor(element) {
    this.element = element;
  }

  attached() {
    this.fixedAttributeManager = new AttributeManager(this.fixedAnchor);
    if (getBooleanFromAttributeValue(this.mdFixed)) {
      this.fixedAttributeManager.addClasses('navbar-fixed');
    }
  }

  detached() {
    if (getBooleanFromAttributeValue(this.mdFixed)) {
      this.fixedAttributeManager.removeClasses('navbar-fixed');
    }
  }
}
<|endoftext|>


<commit_before>import { StyleSheet } from 'react-native';

export default StyleSheet.create({
  container: {
    backgroundColor: '#4b4b4b',
    flex: 1,
  },

  headerSection: {
    alignItems: 'center',
    backgroundColor: '#333',
    borderBottomColor: '#191d1f',
    borderBottomWidth: 1,
    borderTopColor: 'hsla(0,0,100,0.1)',
    borderTopWidth: 1,
    flexDirection: 'row',
    height: 38,
  },
  arrow: {},
  collapsedSectionArrowImage: {
    width: 4,
    height: 8,
    marginLeft: 10,
    marginRight: 10,
  },
  expandedSectionArrowImage: {
    width: 8,
    height: 4,
    marginLeft: 10,
    marginRight: 10,
  },
  title: {},
  titleLabel: {
    color: 'white',
    fontSize: 14,
    fontFamily: 'Avenir',
    fontWeight: '600',
  },

  childrenSection: {
    paddingTop: 20,
    paddingBottom: 30,
    paddingLeft: 10,
  },
});
<commit_msg>Updated section appearance<commit_after>import { StyleSheet } from 'react-native';

export default StyleSheet.create({
  container: {
    backgroundColor: '#4b4b4b',
    flex: 1,
  },

  headerSection: {
    alignItems: 'center',
    backgroundColor: '#333',
    borderBottomColor: '#191d1f',
    borderBottomWidth: 1,
    borderTopColor: 'hsla(0,0,100,0.1)',
    borderTopWidth: 1,
    flexDirection: 'row',
    height: 38,
  },
  arrow: {},
  collapsedSectionArrowImage: {
    width: 4,
    height: 8,
    marginLeft: 10,
    marginRight: 10,
  },
  expandedSectionArrowImage: {
    width: 8,
    height: 4,
    marginLeft: 10,
    marginRight: 10,
  },
  title: {},
  titleLabel: {
    color: 'white',
    fontSize: 14,
    fontFamily: 'Avenir',
    fontWeight: '600',
  },

  childrenSection: {
    borderBottomColor: '#191d1f',
    borderBottomWidth: 1,
    paddingTop: 20,
    paddingBottom: 30,
    paddingLeft: 10,
  },
});
<|endoftext|>


<commit_before>/**
 * volumetracing_plane_controller.js
 * @flow
 */
/* globals JQueryInputEventObject:false */

import _ from "lodash";
import Store from "oxalis/store";
import { updateDatasetSettingAction } from "oxalis/model/actions/settings_actions";
import Utils from "libs/utils";
import Toast from "libs/toast";
import constants, { OrthoViews } from "oxalis/constants";
import type { OrthoViewType, Point2 } from "oxalis/constants";
import VolumeTracingController from "oxalis/controller/annotations/volumetracing_controller";
import PlaneController from "oxalis/controller/viewmodes/plane_controller";
import type SceneController from "oxalis/controller/scene_controller";
import type Model, { BoundingBoxType } from "oxalis/model";
import type View from "oxalis/view";

class VolumeTracingPlaneController extends PlaneController {

  // See comment in Controller class on general controller architecture.
  //
  // Volume Tracing Plane Controller:
  // Extends Plane controller to add controls that are specific to Volume
  // Tracing.

  volumeTracingController: VolumeTracingController;

  constructor(model: Model, view: View, sceneController: SceneController, volumeTracingController: VolumeTracingController) {
    super(model, view, sceneController);
    this.volumeTracingController = volumeTracingController;

    this.listenTo(this.model.flycam, "positionChanged", () => this.render3dCell(this.model.volumeTracing.getActiveCellId()));
    this.listenTo(this.model.flycam, "zoomStepChanged", () => this.render3dCell(this.model.volumeTracing.getActiveCellId()));

    Store.subscribe(() => {
      this.render3dCell(this.model.volumeTracing.getActiveCellId());
    });

    _.defer(this.adjustSegmentationOpacity);

    this.listenTo(this.model.volumeTracing, "newActiveCell", (id) => {
      id = this.model.volumeTracing.getActiveCellId();
      if (id > 0) {
        this.render3dCell(id);
      }
    });
  }

  simulateTracing = async (): Promise<void> => {
    this.model.volumeTracing.setMode(constants.VOLUME_MODE_TRACE);

    const controls = this.getPlaneMouseControls(OrthoViews.PLANE_XY);
    let pos = (x, y) => ({ x, y });

    controls.leftMouseDown(pos(100, 100), OrthoViews.PLANE_XY, {});
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(200, 100));
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(200, 200));
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(100, 200));
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(100, 100));
    controls.leftMouseUp();
    await Utils.sleep(100);
    pos = this.model.flycam.getPosition();
    pos[2]++;
    this.model.flycam.setPosition(pos);
    await Utils.sleep(100);
    await this.simulateTracing();
  };

  getPlaneMouseControls(planeId: OrthoViewType): Object {
    return _.extend(super.getPlaneMouseControls(planeId), {

      leftDownMove: (delta: Point2, pos: Point2) => {
        const mouseInversionX = Store.getState().userConfiguration.inverseX ? 1 : -1;
        const mouseInversionY = Store.getState().userConfiguration.inverseY ? 1 : -1;

        if (this.model.volumeTracing.mode === constants.VOLUME_MODE_MOVE) {
          this.move([
            (delta.x * mouseInversionX) / this.planeView.scaleFactor,
            (delta.y * mouseInversionY) / this.planeView.scaleFactor,
            0,
          ]);
        } else {
          this.model.volumeTracing.addToLayer(this.calculateGlobalPos(pos));
        }
      },

      leftMouseDown: (pos: Point2, plane: OrthoViewType, event: JQueryInputEventObject) => {
        if (event.shiftKey) {
          this.volumeTracingController.enterDeleteMode();
        }
        this.model.volumeTracing.startEditing(plane);
      },

      leftMouseUp: () => {
        this.model.volumeTracing.finishLayer();
        this.volumeTracingController.restoreAfterDeleteMode();
      },

      rightDownMove: (delta: Point2, pos: Point2) => {
        if (this.model.volumeTracing.mode === constants.VOLUME_MODE_TRACE) {
          this.model.volumeTracing.addToLayer(this.calculateGlobalPos(pos));
        }
      },

      rightMouseDown: (pos: Point2, plane: OrthoViewType) => {
        this.volumeTracingController.enterDeleteMode();
        this.model.volumeTracing.startEditing(plane);
      },

      rightMouseUp: () => {
        this.model.volumeTracing.finishLayer();
        this.volumeTracingController.restoreAfterDeleteMode();
      },

      leftClick: (pos: Point2) => {
        const cellId = this.model.getSegmentationBinary().cube.getDataValue(this.calculateGlobalPos(pos));

        this.volumeTracingController.handleCellSelection(cellId);
      },
    });
  }

  adjustSegmentationOpacity(): void {
    if (Store.getState().datasetConfiguration.segmentationOpacity < 10) {
      Toast.error("Your setting for \"segmentation opacity\" is set very low. Increase it for more visibility while volume tracing.");
      Store.dispatch(updateDatasetSettingAction("segmentationOpacity", 10));
    }
  }

  getKeyboardControls(): Object {
    return _.extend(super.getKeyboardControls(), {
      c: () => this.model.volumeTracing.createCell(),
    });
  }

  render3dCell(id: number): void {
    if (!Store.getState().userConfiguration.isosurfaceDisplay) {
      this.sceneController.removeShapes();
      return;
    }
    const bb = this.model.flycam.getViewportBoundingBox();
    const res = Store.getState().userConfiguration.isosurfaceResolution;
    this.sceneController.showShapes(this.scaleIsosurfaceBB(bb), res, id);
  }

  scaleIsosurfaceBB(bb: BoundingBoxType): BoundingBoxType {
    const factor = Store.getState().userConfiguration.isosurfaceBBsize;
    for (let i = 0; i <= 2; i++) {
      const width = bb.max[i] - bb.min[i];
      const diff = ((factor - 1) * width) / 2;
      bb.min[i] -= diff;
      bb.max[i] += diff;
    }
    return bb;
  }
}

export default VolumeTracingPlaneController;
<commit_msg>removes segmentation opacity reset<commit_after>/**
 * volumetracing_plane_controller.js
 * @flow
 */
/* globals JQueryInputEventObject:false */

import _ from "lodash";
import Store from "oxalis/store";
import Utils from "libs/utils";
import Toast from "libs/toast";
import constants, { OrthoViews } from "oxalis/constants";
import type { OrthoViewType, Point2 } from "oxalis/constants";
import VolumeTracingController from "oxalis/controller/annotations/volumetracing_controller";
import PlaneController from "oxalis/controller/viewmodes/plane_controller";
import type SceneController from "oxalis/controller/scene_controller";
import type Model, { BoundingBoxType } from "oxalis/model";
import type View from "oxalis/view";

class VolumeTracingPlaneController extends PlaneController {

  // See comment in Controller class on general controller architecture.
  //
  // Volume Tracing Plane Controller:
  // Extends Plane controller to add controls that are specific to Volume
  // Tracing.

  volumeTracingController: VolumeTracingController;

  constructor(model: Model, view: View, sceneController: SceneController, volumeTracingController: VolumeTracingController) {
    super(model, view, sceneController);
    this.volumeTracingController = volumeTracingController;

    this.listenTo(this.model.flycam, "positionChanged", () => this.render3dCell(this.model.volumeTracing.getActiveCellId()));
    this.listenTo(this.model.flycam, "zoomStepChanged", () => this.render3dCell(this.model.volumeTracing.getActiveCellId()));

    Store.subscribe(() => {
      this.render3dCell(this.model.volumeTracing.getActiveCellId());
    });

    // TODO: This should be put in a saga with `take('INITIALIZE_SETTINGS')`as pre-condition
    setTimeout(this.adjustSegmentationOpacity, 500);

    this.listenTo(this.model.volumeTracing, "newActiveCell", (id) => {
      id = this.model.volumeTracing.getActiveCellId();
      if (id > 0) {
        this.render3dCell(id);
      }
    });
  }

  simulateTracing = async (): Promise<void> => {
    this.model.volumeTracing.setMode(constants.VOLUME_MODE_TRACE);

    const controls = this.getPlaneMouseControls(OrthoViews.PLANE_XY);
    let pos = (x, y) => ({ x, y });

    controls.leftMouseDown(pos(100, 100), OrthoViews.PLANE_XY, {});
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(200, 100));
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(200, 200));
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(100, 200));
    await Utils.sleep(100);
    controls.leftDownMove(null, pos(100, 100));
    controls.leftMouseUp();
    await Utils.sleep(100);
    pos = this.model.flycam.getPosition();
    pos[2]++;
    this.model.flycam.setPosition(pos);
    await Utils.sleep(100);
    await this.simulateTracing();
  };

  getPlaneMouseControls(planeId: OrthoViewType): Object {
    return _.extend(super.getPlaneMouseControls(planeId), {

      leftDownMove: (delta: Point2, pos: Point2) => {
        const mouseInversionX = Store.getState().userConfiguration.inverseX ? 1 : -1;
        const mouseInversionY = Store.getState().userConfiguration.inverseY ? 1 : -1;

        if (this.model.volumeTracing.mode === constants.VOLUME_MODE_MOVE) {
          this.move([
            (delta.x * mouseInversionX) / this.planeView.scaleFactor,
            (delta.y * mouseInversionY) / this.planeView.scaleFactor,
            0,
          ]);
        } else {
          this.model.volumeTracing.addToLayer(this.calculateGlobalPos(pos));
        }
      },

      leftMouseDown: (pos: Point2, plane: OrthoViewType, event: JQueryInputEventObject) => {
        if (event.shiftKey) {
          this.volumeTracingController.enterDeleteMode();
        }
        this.model.volumeTracing.startEditing(plane);
      },

      leftMouseUp: () => {
        this.model.volumeTracing.finishLayer();
        this.volumeTracingController.restoreAfterDeleteMode();
      },

      rightDownMove: (delta: Point2, pos: Point2) => {
        if (this.model.volumeTracing.mode === constants.VOLUME_MODE_TRACE) {
          this.model.volumeTracing.addToLayer(this.calculateGlobalPos(pos));
        }
      },

      rightMouseDown: (pos: Point2, plane: OrthoViewType) => {
        this.volumeTracingController.enterDeleteMode();
        this.model.volumeTracing.startEditing(plane);
      },

      rightMouseUp: () => {
        this.model.volumeTracing.finishLayer();
        this.volumeTracingController.restoreAfterDeleteMode();
      },

      leftClick: (pos: Point2) => {
        const cellId = this.model.getSegmentationBinary().cube.getDataValue(this.calculateGlobalPos(pos));

        this.volumeTracingController.handleCellSelection(cellId);
      },
    });
  }

  adjustSegmentationOpacity(): void {
    if (Store.getState().datasetConfiguration.segmentationOpacity < 10) {
      Toast.warning("Your setting for \"segmentation opacity\" is set very low.<br />Increase it for better visibility while volume tracing.");
    }
  }

  getKeyboardControls(): Object {
    return _.extend(super.getKeyboardControls(), {
      c: () => this.model.volumeTracing.createCell(),
    });
  }

  render3dCell(id: number): void {
    if (!Store.getState().userConfiguration.isosurfaceDisplay) {
      this.sceneController.removeShapes();
      return;
    }
    const bb = this.model.flycam.getViewportBoundingBox();
    const res = Store.getState().userConfiguration.isosurfaceResolution;
    this.sceneController.showShapes(this.scaleIsosurfaceBB(bb), res, id);
  }

  scaleIsosurfaceBB(bb: BoundingBoxType): BoundingBoxType {
    const factor = Store.getState().userConfiguration.isosurfaceBBsize;
    for (let i = 0; i <= 2; i++) {
      const width = bb.max[i] - bb.min[i];
      const diff = ((factor - 1) * width) / 2;
      bb.min[i] -= diff;
      bb.max[i] += diff;
    }
    return bb;
  }
}

export default VolumeTracingPlaneController;
<|endoftext|>