<commit_before>// make the styles we load accessible everywhere;
var styles;

var unhighlighted = d3.rgb("#9999bb");
var unhighlighted_opacity = 0.5;
var highlighted = d3.rgb("#000000");

var tooltipwidth  = 80;
var tooltipheight = 20;

var expanded_tooltipwidth = 180;
var expanded_tooltipheight = 80;

var tooltipoffset_x = 10;
var tooltipoffset_y = 10;

var tooltip_is_shrunk = true;

var circle_radius = 3;
// Parameters for top-level sizing of plot
var blog_or_feature = window.blog_or_feature;
var desired_height = 350;
var div_selector = "#line-chart";

var max_spending = 70000000;
var max_circle_size = 25;

var rows_to_read = 50;

var data_series = [];

var yFormatter = d3.format(",.0$");

var parseDate = d3.time.format("%m/%d/%Y").parse;

var base_width = 950;
if (blog_or_feature == 'blog') {
    base_width = 720
}
if (blog_or_feature == 'narrow') {
    base_width = 650
}
var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = base_width - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var r_color;
var d_color;
var i_color;

function get_x_offset(x) {
    current_tooltipwidth = tooltipwidth;
    if (tooltip_is_shrunk) {
        current_tooltipwidth = tooltipwidth;
    } else {
        current_tooltipwidth = expanded_tooltipwidth;
    }

	if (x >  width - current_tooltipwidth - 20) {
		return x - current_tooltipwidth -20 ;
	} else {
		return x + 20  ;
		}
	}

function get_y_offset(y) {

    current_tooltipheight = tooltipheight;
    if (tooltip_is_shrunk) {
        current_tooltipheight = tooltipheight;
    } else {
        current_tooltipheight = expanded_tooltipheight;
    }

	if (y <  current_tooltipheight ) {
		return y + current_tooltipheight;
	} else {
		return y - current_tooltipheight;
		}
	}

function roundwCommas(nStr) {
    nStr = Math.round(nStr);
    nStr += '';
    x1 = nStr;
    //x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1;
}

d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

var tooltipdiv = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0)
    .style("width", tooltipwidth + "px")
    .style("height", tooltipheight + "px");

function fix_date(element, index, array) {
    array[index] = parseDate(element);
}

function expand_tooltip() {
    d3.select(".tooltip")
        .style("width", expanded_tooltipwidth + "px")
        .style("height", expanded_tooltipheight + "px");
        tooltip_is_shrunk = false;
}

function shrink_tooltip() {
    d3.select(".tooltip")
        .style("width", tooltipwidth + "px")
        .style("height", tooltipheight + "px");
        tooltip_is_shrunk = true;
}

function null_or_float(string_input) {
    result = parseFloat(string_input);
    if (isNaN(result)) {
        result = 0;
    }
    return result;
}

function get_fill_color(d) {
    if (d.political_leaning == 'D') {
        return d_color;
    }
    if (d.political_leaning == 'R') {
        return r_color;
    }
    return i_color;

}

var circle_scale = d3.scale.sqrt().domain([ 0, max_spending ]).range([ 1, max_circle_size ]);

function read_data(text) {
    rows = d3.csv.parseRows(text);
    header_row = rows[1];
    var num_cols = header_row.length;

    data_rows = rows.slice(2,rows.length);

    // just pull out the rows we care about
    for (var i=0; i<rows_to_read; i++) {
            var this_line = {'fec_id':data_rows[i][0], 'name':data_rows[i][1], 'political_leaning':data_rows[i][5], 'roi':100*parseFloat(data_rows[i][7]), 'support_winners':parseFloat(data_rows[i][8]),'oppose_losers':parseFloat(data_rows[i][9]), 'support_losers':parseFloat(data_rows[i][10]), 'oppose_winners':parseFloat(data_rows[i][11]), 'support_unclassified':parseFloat(data_rows[i][12]), 'oppose_unclassified':parseFloat(data_rows[i][13]) };
            this_line['total_positive'] = this_line['support_winners'] + this_line['support_losers'] + this_line['support_unclassified'];
            this_line['total_negative'] = this_line['oppose_winners'] + this_line['oppose_losers'] + this_line['oppose_unclassified'];
            this_line['total_spending'] = this_line['total_positive'] + this_line['total_negative'];
            this_line['fraction_positive'] = 100.0*(0.0+this_line['total_positive'])/(0.0+this_line['total_spending'])
            data_series.push( this_line);
    };

    var svg = d3.select(div_selector+' svg');
    var chart = d3.select(".chart-area");

 //  ... get default margins from specs
    var margin = styles.plot_elements.canvas.margin;

    //  ... create invisible text object
    var testText = svg.append("g")
                    .append("text")
                      .classed("test-text", "true")
                      .classed("axis", "true")
                      .text(function(d){ return "XXXX" });

    var longestLabel = testText[0][0].getBBox().width
    //  ... measure width of invisible text object
    var yLabelWidth = Math.max(longestLabel,0)

    testText.data([]).exit().remove();

    //  ... use larger of two margins
    var suggestedLeftMargin = yLabelWidth + parseInt(styles.text_styles.axis_title['font-size']) + styles.plot_elements.axis.title_padding;

    margin.left = Math.max(margin.left, suggestedLeftMargin);

    //  ... follow D3 margin convention as normal
    var width = styles.plot_elements.canvas.width[blog_or_feature] - margin.left - margin.right,
        height = desired_height - margin.top - margin.bottom;

    svg.attr("width", width + margin.left + margin.right)
       .attr("height", height + margin.top + margin.bottom);

// chart-area
   chart.on("mousemove", function(){
     coordinates = d3.mouse(this);
     var x = d3.event.pageX;
     var y = d3.event.pageY;

     xwindow =  get_x_offset(x);
     ywindow =  get_y_offset(y);
     tooltipdiv.style("left", xwindow + "px")
               .style("top", ywindow + "px")
     }) ;

   var x = d3.scale.linear()
           .range([0, width]);

   var y = d3.scale.linear()
           .range([height, 0]);

   x.domain([-5, 105]);
   y.domain([-5, 105]);

   /*
    * Creating Axes and Gridlines (innerTick)
    */
   var xAxis = d3.svg.axis()
       .ticks(10)
       .scale(x)
       .innerTickSize(-height) // really long ticks become gridlines
       .outerTickSize(0)
       .tickPadding(5)
       .tickFormat(function(d){ return d + "%"})
       .orient("bottom");

   var yAxis = d3.svg.axis()
       .scale(y)
       .orient("left")
       .innerTickSize(-width) // really long ticks become gridlines
       .outerTickSize(0)
       .tickPadding(5)
       .tickFormat(function(d){ return d + "%"});

   /*
    * Drawing chart
    */
   var lineChart = svg.append("g")
       .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

   //  ... add x axis
   lineChart.append("g")
       .attr("class", "x axis")
       .attr("transform", "translate(0," + height + ")")
       .call(xAxis)
     .append("text")
       .classed("title",true)
       .attr("x", function() { return (width / 2.0);}) // anchors title in middle of chart
       // subtract a few pixels so bottom of letters aren't cut off
       .attr("y", function() { return (margin.bottom - 3);})
       .style("text-anchor", "middle") // centers title around anchor
       .text("Return on investment for general election independent expenditures");

   //  ... add y axis with value labels
    lineChart.append("g")
        .attr("class", "y axis")
        .call(yAxis)
      .append("text")
        .classed("title",true)
        .attr("transform", "rotate(-90)")
        .attr("x", function() { return -(height / 2.0);})
        .attr("y", function() { return -(margin.left - styles.plot_elements.axis.label_padding);})
        .attr("dy", function() { return styles.text_styles.axis_label['font-size']; })
        .style("text-anchor", "middle")
        .text("Fraction of general election spending positive");

    lineChart.selectAll("circle")
        .data(data_series)
      .enter().append("circle")
        .attr("cx", function(d) { return x(d.roi); })
        .attr("cy", function(d) { return y(d.fraction_positive); })
        .attr("r", function(d) { return circle_scale(d.total_spending) })
        .attr("name", function(d) { return d.name; })
        .attr("fill", function(d) { return get_fill_color(d); })
        .attr("stroke", "white")
        .attr("stroke-width", 1)

        .on("mouseover", function(f,g){
          d3.select(this).attr("stroke", "black").attr("stroke-width", 2);
          var thistext = "
" + f.name + "
Return on investment: " + f.roi + "%
Positive spending: " + roundwCommas(f.fraction_positive) + "%
Total spending: $" + roundwCommas(f.total_spending);
          tooltipdiv.style("opacity",1);
            tooltipdiv.html( thistext);
            expand_tooltip();

          })
          .on("mouseout", function(){
          d3.select(this).attr("stroke", "white").attr("stroke-width", 1);
          tooltipdiv.style("opacity",0);
          shrink_tooltip();

          })

}

d3.json(window.jsonURL, function(error, s) {

        styles = s;

        // Styles are too faint
        // r_color = styles.colors.data.parties.republican.hex;
        // d_color = styles.colors.data.parties.democrat.hex;
        r_color = "#FF0000";
        d_color = "#0000FF";
        i_color = styles.colors.network_graph['mints'][0].hex;

        // grab the data as text, we'll parse the rows out later
        d3.text("/static/data/roi.csv", read_data);
        //d3.text("/static/realtimefec/js/roi.csv", read_data);

});<commit_msg>try colors again<commit_after>// make the styles we load accessible everywhere;
var styles;

var unhighlighted = d3.rgb("#9999bb");
var unhighlighted_opacity = 0.5;
var highlighted = d3.rgb("#000000");

var tooltipwidth  = 80;
var tooltipheight = 20;

var expanded_tooltipwidth = 180;
var expanded_tooltipheight = 80;

var tooltipoffset_x = 10;
var tooltipoffset_y = 10;

var tooltip_is_shrunk = true;

var circle_radius = 3;
// Parameters for top-level sizing of plot
var blog_or_feature = window.blog_or_feature;
var desired_height = 350;
var div_selector = "#line-chart";

var max_spending = 70000000;
var max_circle_size = 25;

var rows_to_read = 50;

var data_series = [];

var yFormatter = d3.format(",.0$");

var parseDate = d3.time.format("%m/%d/%Y").parse;

var base_width = 950;
if (blog_or_feature == 'blog') {
    base_width = 720
}
if (blog_or_feature == 'narrow') {
    base_width = 650
}
var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = base_width - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var r_color;
var d_color;
var i_color;

function get_x_offset(x) {
    current_tooltipwidth = tooltipwidth;
    if (tooltip_is_shrunk) {
        current_tooltipwidth = tooltipwidth;
    } else {
        current_tooltipwidth = expanded_tooltipwidth;
    }

	if (x >  width - current_tooltipwidth - 20) {
		return x - current_tooltipwidth -20 ;
	} else {
		return x + 20  ;
		}
	}

function get_y_offset(y) {

    current_tooltipheight = tooltipheight;
    if (tooltip_is_shrunk) {
        current_tooltipheight = tooltipheight;
    } else {
        current_tooltipheight = expanded_tooltipheight;
    }

	if (y <  current_tooltipheight ) {
		return y + current_tooltipheight;
	} else {
		return y - current_tooltipheight;
		}
	}

function roundwCommas(nStr) {
    nStr = Math.round(nStr);
    nStr += '';
    x1 = nStr;
    //x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1;
}

d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

var tooltipdiv = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0)
    .style("width", tooltipwidth + "px")
    .style("height", tooltipheight + "px");

function fix_date(element, index, array) {
    array[index] = parseDate(element);
}

function expand_tooltip() {
    d3.select(".tooltip")
        .style("width", expanded_tooltipwidth + "px")
        .style("height", expanded_tooltipheight + "px");
        tooltip_is_shrunk = false;
}

function shrink_tooltip() {
    d3.select(".tooltip")
        .style("width", tooltipwidth + "px")
        .style("height", tooltipheight + "px");
        tooltip_is_shrunk = true;
}

function null_or_float(string_input) {
    result = parseFloat(string_input);
    if (isNaN(result)) {
        result = 0;
    }
    return result;
}

function get_fill_color(d) {
    if (d.political_leaning == 'D') {
        return d_color;
    }
    if (d.political_leaning == 'R') {
        return r_color;
    }
    return i_color;

}

var circle_scale = d3.scale.sqrt().domain([ 0, max_spending ]).range([ 1, max_circle_size ]);

function read_data(text) {
    rows = d3.csv.parseRows(text);
    header_row = rows[1];
    var num_cols = header_row.length;

    data_rows = rows.slice(2,rows.length);

    // just pull out the rows we care about
    for (var i=0; i<rows_to_read; i++) {
            var this_line = {'fec_id':data_rows[i][0], 'name':data_rows[i][1], 'political_leaning':data_rows[i][5], 'roi':100*parseFloat(data_rows[i][7]), 'support_winners':parseFloat(data_rows[i][8]),'oppose_losers':parseFloat(data_rows[i][9]), 'support_losers':parseFloat(data_rows[i][10]), 'oppose_winners':parseFloat(data_rows[i][11]), 'support_unclassified':parseFloat(data_rows[i][12]), 'oppose_unclassified':parseFloat(data_rows[i][13]) };
            this_line['total_positive'] = this_line['support_winners'] + this_line['support_losers'] + this_line['support_unclassified'];
            this_line['total_negative'] = this_line['oppose_winners'] + this_line['oppose_losers'] + this_line['oppose_unclassified'];
            this_line['total_spending'] = this_line['total_positive'] + this_line['total_negative'];
            this_line['fraction_positive'] = 100.0*(0.0+this_line['total_positive'])/(0.0+this_line['total_spending'])
            data_series.push( this_line);
    };

    var svg = d3.select(div_selector+' svg');
    var chart = d3.select(".chart-area");

 //  ... get default margins from specs
    var margin = styles.plot_elements.canvas.margin;

    //  ... create invisible text object
    var testText = svg.append("g")
                    .append("text")
                      .classed("test-text", "true")
                      .classed("axis", "true")
                      .text(function(d){ return "XXXX" });

    var longestLabel = testText[0][0].getBBox().width
    //  ... measure width of invisible text object
    var yLabelWidth = Math.max(longestLabel,0)

    testText.data([]).exit().remove();

    //  ... use larger of two margins
    var suggestedLeftMargin = yLabelWidth + parseInt(styles.text_styles.axis_title['font-size']) + styles.plot_elements.axis.title_padding;

    margin.left = Math.max(margin.left, suggestedLeftMargin);

    //  ... follow D3 margin convention as normal
    var width = styles.plot_elements.canvas.width[blog_or_feature] - margin.left - margin.right,
        height = desired_height - margin.top - margin.bottom;

    svg.attr("width", width + margin.left + margin.right)
       .attr("height", height + margin.top + margin.bottom);

// chart-area
   chart.on("mousemove", function(){
     coordinates = d3.mouse(this);
     var x = d3.event.pageX;
     var y = d3.event.pageY;

     xwindow =  get_x_offset(x);
     ywindow =  get_y_offset(y);
     tooltipdiv.style("left", xwindow + "px")
               .style("top", ywindow + "px")
     }) ;

   var x = d3.scale.linear()
           .range([0, width]);

   var y = d3.scale.linear()
           .range([height, 0]);

   x.domain([-5, 105]);
   y.domain([-5, 105]);

   /*
    * Creating Axes and Gridlines (innerTick)
    */
   var xAxis = d3.svg.axis()
       .ticks(10)
       .scale(x)
       .innerTickSize(-height) // really long ticks become gridlines
       .outerTickSize(0)
       .tickPadding(5)
       .tickFormat(function(d){ return d + "%"})
       .orient("bottom");

   var yAxis = d3.svg.axis()
       .scale(y)
       .orient("left")
       .innerTickSize(-width) // really long ticks become gridlines
       .outerTickSize(0)
       .tickPadding(5)
       .tickFormat(function(d){ return d + "%"});

   /*
    * Drawing chart
    */
   var lineChart = svg.append("g")
       .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

   //  ... add x axis
   lineChart.append("g")
       .attr("class", "x axis")
       .attr("transform", "translate(0," + height + ")")
       .call(xAxis)
     .append("text")
       .classed("title",true)
       .attr("x", function() { return (width / 2.0);}) // anchors title in middle of chart
       // subtract a few pixels so bottom of letters aren't cut off
       .attr("y", function() { return (margin.bottom - 3);})
       .style("text-anchor", "middle") // centers title around anchor
       .text("Return on investment for general election independent expenditures");

   //  ... add y axis with value labels
    lineChart.append("g")
        .attr("class", "y axis")
        .call(yAxis)
      .append("text")
        .classed("title",true)
        .attr("transform", "rotate(-90)")
        .attr("x", function() { return -(height / 2.0);})
        .attr("y", function() { return -(margin.left - styles.plot_elements.axis.label_padding);})
        .attr("dy", function() { return styles.text_styles.axis_label['font-size']; })
        .style("text-anchor", "middle")
        .text("Fraction of general election spending positive");

    lineChart.selectAll("circle")
        .data(data_series)
      .enter().append("circle")
        .attr("cx", function(d) { return x(d.roi); })
        .attr("cy", function(d) { return y(d.fraction_positive); })
        .attr("r", function(d) { return circle_scale(d.total_spending) })
        .attr("name", function(d) { return d.name; })
        .attr("fill", function(d) { return get_fill_color(d); })
        .attr("stroke", "white")
        .attr("stroke-width", 1)

        .on("mouseover", function(f,g){
          d3.select(this).attr("stroke", "black").attr("stroke-width", 2);
          var thistext = "
" + f.name + "
Return on investment: " + f.roi + "%
Positive spending: " + roundwCommas(f.fraction_positive) + "%
Total spending: $" + roundwCommas(f.total_spending);
          tooltipdiv.style("opacity",1);
            tooltipdiv.html( thistext);
            expand_tooltip();

          })
          .on("mouseout", function(){
          d3.select(this).attr("stroke", "white").attr("stroke-width", 1);
          tooltipdiv.style("opacity",0);
          shrink_tooltip();

          })

}

d3.json(window.jsonURL, function(error, s) {

        styles = s;

        // Styles are too faint
        r_color = styles.colors.data.parties.republican.hex;
        d_color = styles.colors.data.parties.democrat.hex;
        //r_color = "#FF0000";
        //d_color = "#0000FF";
        i_color = styles.colors.network_graph['mints'][0].hex;

        // grab the data as text, we'll parse the rows out later
        d3.text("/static/data/roi.csv", read_data);
        //d3.text("/static/realtimefec/js/roi.csv", read_data);

});<|endoftext|>


<commit_before>    require("colors");

var path = require("path"),
    glob = require("glob"),
    execSync = require("execSync"),
    util = require("util"),
    fs = require("fs"),

    resultFileNamePostFix = "_results_for_%s.json",
    parametrizedResultFileNamePostFix = "_results_for_%s_parameters(%s).json",
    deprecatedFragment = "tracking_result",

    command = './bin/tracking --include-results %s "%s" > %s',
    paramterizedCommand = './bin/tracking --include-results %s "%s" %s > "%s"',

    algorithms = [
      "Sparse Optical Flow",
      "Dense Optical Flow",
      "Random Forest Tracker"
    ],

    parametrized = JSON.parse(fs.readFileSync("parametrized.json")).builds || [],

    pathWithoutExtension = function(element) {
      return element.replace(path.extname(element), "");
    },

    sanitize = function(name) {
      return name.replace(/\s/gi, "_");
    },

    getProperDate = function() {
      var zeroPad = function(input) {
            return input[1] ? input : "0" + input[0];
          },

          date = new Date(),

          yyyy = date.getFullYear().toString(),
          mm = (date.getMonth() + 1).toString(),
          dd  = date.getDate().toString(),

          HH = date.getHours().toString(),
          MM = date.getMinutes().toString(),
          ss = date.getSeconds().toString();

      return util.format("%s_%s_%s_%s_%s_%s",
                         yyyy, zeroPad(mm), zeroPad(dd), zeroPad(HH), zeroPad(MM), zeroPad(ss));
    },

    backup = function(prefix) {
      var folderName = (prefix || "") + "tests_with_results_from_" + getProperDate();

      console.log("Backuping movie files with results...".rainbow);

      execSync.exec("mkdir assets/backup/" + folderName);

      execSync.exec("mv assets/*_tracking_result_for_*.avi assets/backup/" + folderName);
      execSync.exec("mv assets/*_results_for_*.json assets/backup/" + folderName);
    };

console.log("Compiling ./bin/tracking in release mode...".rainbow);

if (execSync.code("scons") === 0) {

  backup("before_");

  algorithms.forEach(function(algorithm) {
    glob.sync("./assets/*.avi").forEach(function(fileName) {
      var resultFileName = pathWithoutExtension(fileName) +
                           util.format(resultFileNamePostFix, sanitize(algorithm)),
          builtCommand = util.format(command, fileName, algorithm, resultFileName);

      if (fileName.indexOf(deprecatedFragment) === -1) {
        console.log("Processing '%s' file with algorithm '%s'".yellow, fileName, algorithm);

        if (execSync.code(builtCommand) === 0) {
          console.log("Results saved to file '%s'".green, resultFileName)
        } else {
          console.log("[EE] Command for file name '%s' and algorithm '%s' failed!".red,
                      fileName, algorithm);
        }
      }
    });
  });

  backup();

  parametrized.forEach(function(object) {
    var resultFileName = pathWithoutExtension(object.fileName) +
                         util.format(parametrizedResultFileNamePostFix, sanitize(object.algorithm), object.params),
        builtCommand = util.format(paramterizedCommand,
                                   object.fileName, object.algorithm, object.params, resultFileName);

    if (object.fileName.indexOf(deprecatedFragment) === -1) {
      console.log("Processing '%s' file with algorithm '%s' and parameters '%s'".yellow,
                  object.fileName, object.algorithm, object.params);

      if (execSync.code(builtCommand) === 0) {
        console.log("Results saved to file '%s'".green, resultFileName)
      } else {
        console.log("[EE] Command for file name '%s', algorithm '%s' and parameters '%s' failed!".red,
                    object.fileName, object.algorithm, object.params);
      }
    }
  });

  backup("parametrized_");

}<commit_msg>Small fix (missing semicolons).<commit_after>    require("colors");

var path = require("path"),
    glob = require("glob"),
    execSync = require("execSync"),
    util = require("util"),
    fs = require("fs"),

    resultFileNamePostFix = "_results_for_%s.json",
    parametrizedResultFileNamePostFix = "_results_for_%s_parameters(%s).json",
    deprecatedFragment = "tracking_result",

    command = './bin/tracking --include-results %s "%s" > %s',
    paramterizedCommand = './bin/tracking --include-results %s "%s" %s > "%s"',

    algorithms = [
      "Sparse Optical Flow",
      "Dense Optical Flow",
      "Random Forest Tracker"
    ],

    parametrized = JSON.parse(fs.readFileSync("parametrized.json")).builds || [],

    pathWithoutExtension = function(element) {
      return element.replace(path.extname(element), "");
    },

    sanitize = function(name) {
      return name.replace(/\s/gi, "_");
    },

    getProperDate = function() {
      var zeroPad = function(input) {
            return input[1] ? input : "0" + input[0];
          },

          date = new Date(),

          yyyy = date.getFullYear().toString(),
          mm = (date.getMonth() + 1).toString(),
          dd  = date.getDate().toString(),

          HH = date.getHours().toString(),
          MM = date.getMinutes().toString(),
          ss = date.getSeconds().toString();

      return util.format("%s_%s_%s_%s_%s_%s",
                         yyyy, zeroPad(mm), zeroPad(dd), zeroPad(HH), zeroPad(MM), zeroPad(ss));
    },

    backup = function(prefix) {
      var folderName = (prefix || "") + "tests_with_results_from_" + getProperDate();

      console.log("Backuping movie files with results...".rainbow);

      execSync.exec("mkdir assets/backup/" + folderName);

      execSync.exec("mv assets/*_tracking_result_for_*.avi assets/backup/" + folderName);
      execSync.exec("mv assets/*_results_for_*.json assets/backup/" + folderName);
    };

console.log("Compiling ./bin/tracking in release mode...".rainbow);

if (execSync.code("scons") === 0) {

  backup("before_");

  algorithms.forEach(function(algorithm) {
    glob.sync("./assets/*.avi").forEach(function(fileName) {
      var resultFileName = pathWithoutExtension(fileName) +
                           util.format(resultFileNamePostFix, sanitize(algorithm)),
          builtCommand = util.format(command, fileName, algorithm, resultFileName);

      if (fileName.indexOf(deprecatedFragment) === -1) {
        console.log("Processing '%s' file with algorithm '%s'".yellow, fileName, algorithm);

        if (execSync.code(builtCommand) === 0) {
          console.log("Results saved to file '%s'".green, resultFileName);
        } else {
          console.log("[EE] Command for file name '%s' and algorithm '%s' failed!".red,
                      fileName, algorithm);
        }
      }
    });
  });

  backup();

  parametrized.forEach(function(object) {
    var resultFileName = pathWithoutExtension(object.fileName) +
                         util.format(parametrizedResultFileNamePostFix, sanitize(object.algorithm), object.params),
        builtCommand = util.format(paramterizedCommand,
                                   object.fileName, object.algorithm, object.params, resultFileName);

    if (object.fileName.indexOf(deprecatedFragment) === -1) {
      console.log("Processing '%s' file with algorithm '%s' and parameters '%s'".yellow,
                  object.fileName, object.algorithm, object.params);

      if (execSync.code(builtCommand) === 0) {
        console.log("Results saved to file '%s'".green, resultFileName);
      } else {
        console.log("[EE] Command for file name '%s', algorithm '%s' and parameters '%s' failed!".red,
                    object.fileName, object.algorithm, object.params);
      }
    }
  });

  backup("parametrized_");

}
<|endoftext|>


<commit_before>// @noflow

'use strict';

const util = require('util');
const https = require('https');
const { exec } = require('./utils');
const packageJSON = require('../package.json');

const graphqlRequest = util.promisify(graphqlRequestImpl);
const labelsConfig = {
  'PR: breaking change 💥': {
    section: 'Breaking Change 💥',
  },
  'PR: feature 🚀': {
    section: 'New Feature 🚀',
  },
  'PR: bug fix 🐞': {
    section: 'Bug Fix 🐞',
  },
  'PR: docs 📝': {
    section: 'Docs 📝',
    fold: true,
  },
  'PR: polish 💅': {
    section: 'Polish 💅',
    fold: true,
  },
  'PR: internal 🏠': {
    section: 'Internal 🏠',
    fold: true,
  },
  'PR: dependency 📦': {
    section: 'Dependency 📦',
    fold: true,
  },
};
const GH_TOKEN = process.env['GH_TOKEN'];

if (!GH_TOKEN) {
  console.error('Must provide GH_TOKEN as enviroment variable!');
  process.exit(1);
}

getChangeLog()
  .then(changelog => process.stdout.write(changelog))
  .catch(error => console.error(error));

function getChangeLog() {
  const { version } = packageJSON;

  let tag = null;
  let commitsList = exec(`git rev-list --reverse v${version}..`);
  if (commitsList === '') {
    const parentPackageJSON = exec('git cat-file blob HEAD~1:package.json');
    const parentVersion = JSON.parse(parentPackageJSON).version;
    commitsList = exec(`git rev-list --reverse v${parentVersion}..HEAD~1`);
    tag = `v${version}`;
  }

  const date = exec('git log -1 --format=%cd --date=short');
  return getCommitsInfo(commitsList.split('\n')).then(commitsInfo =>
    genChangeLog(tag, date, commitsInfo),
  );
}

function genChangeLog(tag, date, commitsInfo) {
  const allPRs = commitsInfoToPRs(commitsInfo);
  const byLabel = {};
  const commitersByLogin = {};

  for (const pr of allPRs) {
    if (!labelsConfig[pr.label]) {
      throw new Error('Unknown label: ' + pr.label + pr.number);
    }
    byLabel[pr.label] = byLabel[pr.label] || [];
    byLabel[pr.label].push(pr);
    commitersByLogin[pr.author.login] = pr.author;
  }

  let changelog = `## ${tag || 'Unreleased'} (${date})\n`;
  for (const [label, config] of Object.entries(labelsConfig)) {
    const prs = byLabel[label];
    if (prs) {
      const shouldFold = config.fold && prs.length > 1;

      changelog += `\n#### ${config.section}\n`;
      if (shouldFold) {
        changelog += '\n';
        changelog += `<summary> ${prs.length} PRs were merged </summary>\n\n`;
      }

      for (const pr of prs) {
        const { number, url, author } = pr;
        changelog += `* [#${number}](${url}) ${pr.title} ([@${author.login}](${author.url}))\n`;
      }

      if (shouldFold) {
        changelog += '\n';
      }
    }
  }

  const commiters = Object.values(commitersByLogin).sort((a, b) =>
    (a.name || a.login).localeCompare(b.name || b.login),
  );
  changelog += `\n#### Committers: ${commiters.length}\n`;
  for (const commiter of commiters) {
    changelog += `* ${commiter.name}([@${commiter.login}](${commiter.url}))\n`;
  }

  return changelog;
}

function graphqlRequestImpl(query, variables, cb) {
  const resultCB = typeof variables === 'function' ? variables : cb;

  const req = https.request('https://api.github.com/graphql', {
    method: 'POST',
    headers: {
      Authorization: 'bearer ' + GH_TOKEN,
      'Content-Type': 'application/json',
      'User-Agent': 'graphql-js-changelog',
    },
  });

  req.on('response', res => {
    let responseBody = '';

    res.setEncoding('utf8');
    res.on('data', d => (responseBody += d));
    res.on('error', error => resultCB(error));

    res.on('end', () => {
      if (res.statusCode !== 200) {
        return resultCB(
          new Error(
            `GitHub responded with ${res.statusCode}: ${res.statusMessage}\n` +
              responseBody,
          ),
        );
      }

      let json;
      try {
        json = JSON.parse(responseBody);
      } catch (error) {
        return resultCB(error);
      }

      if (json.errors) {
        return resultCB(
          new Error('Errors: ' + JSON.stringify(json.errors, null, 2)),
        );
      }

      resultCB(undefined, json.data);
    });
  });

  req.on('error', error => cb(error));
  req.write(JSON.stringify({ query, variables }));
  req.end();
}

async function batchCommitInfo(commits) {
  let commitsSubQuery = '';
  for (const oid of commits) {
    commitsSubQuery += `
        commit_${oid}: object(oid: "${oid}") {
          ... on Commit {
            oid
            message
            associatedPullRequests(first: 10) {
              nodes {
                number
                title
                url
                author {
                  login
                  url
                  ... on User {
                    name
                  }
                }
                repository {
                  nameWithOwner
                }
                labels(first: 10) {
                  nodes {
                    name
                  }
                }
              }
            }
          }
        }
    `;
  }

  const response = await graphqlRequest(`
    {
      repository(owner: "graphql", name: "graphql-js") {
        ${commitsSubQuery}
      }
    }
  `);

  const commitsInfo = [];
  for (const oid of commits) {
    commitsInfo.push(response.repository['commit_' + oid]);
  }
  return commitsInfo;
}

function commitsInfoToPRs(commits) {
  const prs = [];
  for (const commit of commits) {
    const associatedPRs = commit.associatedPullRequests.nodes.filter(
      pr => pr.repository.nameWithOwner === 'graphql/graphql-js',
    );
    if (associatedPRs.length === 0) {
      throw new Error(
        `Commit ${commit.oid} has no associated PR: ${commit.message}`,
      );
    }
    if (associatedPRs.length > 1) {
      throw new Error(
        `Commit ${commit.oid} is associated with multiple PRs: ${commit.message}`,
      );
    }

    const pr = associatedPRs[0];
    const labels = pr.labels.nodes
      .map(label => label.name)
      .filter(label => label.startsWith('PR: '));

    if (labels.length === 0) {
      throw new Error(`PR #${pr.number} missing label`);
    }
    if (labels.length > 1) {
      throw new Error(
        `PR #${pr.number} has conflicting labels: ` + labels.join('\n'),
      );
    }
    prs.push({
      number: pr.number,
      title: pr.title,
      url: pr.url,
      author: pr.author,
      label: labels[0],
    });
  }

  return prs;
}

async function getCommitsInfo(commits) {
  // Split commits into batches of 50 to prevent timeouts
  const commitInfoPromises = [];
  for (let i = 0; i < commits.length; i += 50) {
    const batch = commits.slice(i, i + 50);
    commitInfoPromises.push(batchCommitInfo(batch));
  }

  return (await Promise.all(commitInfoPromises)).flat();
}
<commit_msg>changelog: Remove duplicate PRs (#1999)<commit_after>// @noflow

'use strict';

const util = require('util');
const https = require('https');
const { exec } = require('./utils');
const packageJSON = require('../package.json');

const graphqlRequest = util.promisify(graphqlRequestImpl);
const labelsConfig = {
  'PR: breaking change 💥': {
    section: 'Breaking Change 💥',
  },
  'PR: feature 🚀': {
    section: 'New Feature 🚀',
  },
  'PR: bug fix 🐞': {
    section: 'Bug Fix 🐞',
  },
  'PR: docs 📝': {
    section: 'Docs 📝',
    fold: true,
  },
  'PR: polish 💅': {
    section: 'Polish 💅',
    fold: true,
  },
  'PR: internal 🏠': {
    section: 'Internal 🏠',
    fold: true,
  },
  'PR: dependency 📦': {
    section: 'Dependency 📦',
    fold: true,
  },
};
const GH_TOKEN = process.env['GH_TOKEN'];

if (!GH_TOKEN) {
  console.error('Must provide GH_TOKEN as enviroment variable!');
  process.exit(1);
}

getChangeLog()
  .then(changelog => process.stdout.write(changelog))
  .catch(error => console.error(error));

function getChangeLog() {
  const { version } = packageJSON;

  let tag = null;
  let commitsList = exec(`git rev-list --reverse v${version}..`);
  if (commitsList === '') {
    const parentPackageJSON = exec('git cat-file blob HEAD~1:package.json');
    const parentVersion = JSON.parse(parentPackageJSON).version;
    commitsList = exec(`git rev-list --reverse v${parentVersion}..HEAD~1`);
    tag = `v${version}`;
  }

  const date = exec('git log -1 --format=%cd --date=short');
  return getCommitsInfo(commitsList.split('\n')).then(commitsInfo =>
    genChangeLog(tag, date, commitsInfo),
  );
}

function genChangeLog(tag, date, commitsInfo) {
  const allPRs = commitsInfoToPRs(commitsInfo);
  const byLabel = {};
  const commitersByLogin = {};

  for (const pr of allPRs) {
    if (!labelsConfig[pr.label]) {
      throw new Error('Unknown label: ' + pr.label + pr.number);
    }
    byLabel[pr.label] = byLabel[pr.label] || [];
    byLabel[pr.label].push(pr);
    commitersByLogin[pr.author.login] = pr.author;
  }

  let changelog = `## ${tag || 'Unreleased'} (${date})\n`;
  for (const [label, config] of Object.entries(labelsConfig)) {
    const prs = byLabel[label];
    if (prs) {
      const shouldFold = config.fold && prs.length > 1;

      changelog += `\n#### ${config.section}\n`;
      if (shouldFold) {
        changelog += '\n';
        changelog += `<summary> ${prs.length} PRs were merged </summary>\n\n`;
      }

      for (const pr of prs) {
        const { number, url, author } = pr;
        changelog += `* [#${number}](${url}) ${pr.title} ([@${author.login}](${author.url}))\n`;
      }

      if (shouldFold) {
        changelog += '\n';
      }
    }
  }

  const commiters = Object.values(commitersByLogin).sort((a, b) =>
    (a.name || a.login).localeCompare(b.name || b.login),
  );
  changelog += `\n#### Committers: ${commiters.length}\n`;
  for (const commiter of commiters) {
    changelog += `* ${commiter.name}([@${commiter.login}](${commiter.url}))\n`;
  }

  return changelog;
}

function graphqlRequestImpl(query, variables, cb) {
  const resultCB = typeof variables === 'function' ? variables : cb;

  const req = https.request('https://api.github.com/graphql', {
    method: 'POST',
    headers: {
      Authorization: 'bearer ' + GH_TOKEN,
      'Content-Type': 'application/json',
      'User-Agent': 'graphql-js-changelog',
    },
  });

  req.on('response', res => {
    let responseBody = '';

    res.setEncoding('utf8');
    res.on('data', d => (responseBody += d));
    res.on('error', error => resultCB(error));

    res.on('end', () => {
      if (res.statusCode !== 200) {
        return resultCB(
          new Error(
            `GitHub responded with ${res.statusCode}: ${res.statusMessage}\n` +
              responseBody,
          ),
        );
      }

      let json;
      try {
        json = JSON.parse(responseBody);
      } catch (error) {
        return resultCB(error);
      }

      if (json.errors) {
        return resultCB(
          new Error('Errors: ' + JSON.stringify(json.errors, null, 2)),
        );
      }

      resultCB(undefined, json.data);
    });
  });

  req.on('error', error => cb(error));
  req.write(JSON.stringify({ query, variables }));
  req.end();
}

async function batchCommitInfo(commits) {
  let commitsSubQuery = '';
  for (const oid of commits) {
    commitsSubQuery += `
        commit_${oid}: object(oid: "${oid}") {
          ... on Commit {
            oid
            message
            associatedPullRequests(first: 10) {
              nodes {
                number
                title
                url
                author {
                  login
                  url
                  ... on User {
                    name
                  }
                }
                repository {
                  nameWithOwner
                }
                labels(first: 10) {
                  nodes {
                    name
                  }
                }
              }
            }
          }
        }
    `;
  }

  const response = await graphqlRequest(`
    {
      repository(owner: "graphql", name: "graphql-js") {
        ${commitsSubQuery}
      }
    }
  `);

  const commitsInfo = [];
  for (const oid of commits) {
    commitsInfo.push(response.repository['commit_' + oid]);
  }
  return commitsInfo;
}

function commitsInfoToPRs(commits) {
  const prs = {};
  for (const commit of commits) {
    const associatedPRs = commit.associatedPullRequests.nodes.filter(
      pr => pr.repository.nameWithOwner === 'graphql/graphql-js',
    );
    if (associatedPRs.length === 0) {
      throw new Error(
        `Commit ${commit.oid} has no associated PR: ${commit.message}`,
      );
    }
    if (associatedPRs.length > 1) {
      throw new Error(
        `Commit ${commit.oid} is associated with multiple PRs: ${commit.message}`,
      );
    }

    const pr = associatedPRs[0];
    const labels = pr.labels.nodes
      .map(label => label.name)
      .filter(label => label.startsWith('PR: '));

    if (labels.length === 0) {
      throw new Error(`PR #${pr.number} missing label`);
    }
    if (labels.length > 1) {
      throw new Error(
        `PR #${pr.number} has conflicting labels: ` + labels.join('\n'),
      );
    }

    prs[pr.number] = {
      number: pr.number,
      title: pr.title,
      url: pr.url,
      author: pr.author,
      label: labels[0],
    };
  }

  return Object.values(prs);
}

async function getCommitsInfo(commits) {
  // Split commits into batches of 50 to prevent timeouts
  const commitInfoPromises = [];
  for (let i = 0; i < commits.length; i += 50) {
    const batch = commits.slice(i, i + 50);
    commitInfoPromises.push(batchCommitInfo(batch));
  }

  return (await Promise.all(commitInfoPromises)).flat();
}
<|endoftext|>


<commit_before>const BroadlinkRMAccessory = require('./accessory');
const discoveredDevices = require('../helpers/devices.js');
const sendData = require('../helpers/sendData');

class AirConAccessory extends BroadlinkRMAccessory {

  constructor (log, config) {
    super(log, config)

    const { defaultCoolTemperature, defaultHeatTemperature, heatTemperature, minTemperature, maxTemperature, pseudoDeviceTemperature, replaceAutoMode, units } = config

    this.log = log;
    this.currentHeatingCoolingState = Characteristic.CurrentHeatingCoolingState.OFF
    this.targetTemperature = 0
    this.firstTemperatureUpdate = true

    config.minTemperature = minTemperature || 0
    config.maxTemperature = maxTemperature || 30

    config.temperatureDisplayUnits = (units && units.toLowerCase() === 'f') ? Characteristic.TemperatureDisplayUnits.FAHRENHEIT : Characteristic.TemperatureDisplayUnits.CELSIUS;

    // When a temperature hex doesn't exist we try to use the hex set for these
    // default temperatures
    config.defaultCoolTemperature = defaultCoolTemperature || 16
    config.defaultHeatTemperature = defaultHeatTemperature || 30

    // Used to determine when we should use the defaultHeatTemperature or the
    // defaultHeatTemperature
    config.heatTemperature = heatTemperature || 22

    // When we turn on the thermostat with Siri it comes thrugh as "auto" which
    // isn't particularly supported at this time so we convert the mode to cool
    // or heat
    // Note that this is only used when you use Siri or press Auto immediately
    // after launching Homebridge. The rest of the time we'll use your last known
    // temperature
    config.replaceAutoMode = replaceAutoMode || 'cool'

    if (config.pseudoDeviceTemperature > config.minTemperature) throw new Error(`The pseudoDeviceTemperature (${pseudoDeviceTemperature}) must be less than the minTemperature (${minTemperature})`)

    this.callbackQueue = {}
  }

  getServices () {
    const services = super.getServices();
    const { data, config, name } = this;
    const { minTemperature, maxTemperature } = config

    const service = new Service.Thermostat(name);

    // Required Characteristics
    service
      .getCharacteristic(Characteristic.CurrentHeatingCoolingState)
      .on('get', this.getCurrentHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.TargetHeatingCoolingState)
      .on('get', this.getTargetHeatingCoolingState.bind(this))
      .on('set', this.setTargetHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .on('get', this.getCurrentTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .on('get', this.getTargetTemperature.bind(this))
      .on('set', this.setTargetTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TemperatureDisplayUnits)
      .on('get', this.getTemperatureDisplayUnits.bind(this))
      .on('set', this.setTemperatureDisplayUnits.bind(this));

    //
    // service
    //   .getCharacteristic(Characteristic.On)
    //   .on('get', this.getOnOffState.bind(this))
    //   .on('set', this.setOnOffState.bind(this));

    service
      .getCharacteristic(Characteristic.Name)
      .on('get', this.getName.bind(this, name));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    this.thermostatService = service
    services.push(service)

    return services
  }

  updateServiceHeatingCoolingState (value) {
    if (this.currentHeatingCoolingState === value) return

    this.currentHeatingCoolingState = value

    // this.log(`setCharacteristic for CurrentHeatingCoolingState and TargetHeatingCoolingState (${value})`)
    this.thermostatService.setCharacteristic(Characteristic.CurrentHeatingCoolingState, value);
    this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, value);
  }

  // Thermostat
  sendTemperature (temperature, callback) {
    const { config, data, host, log } = this
    const { defaultHeatTemperature, defaultCoolTemperature, heatTemperature } = config

    log(`Potentially sendTemperature (${temperature})`);

    let hasTemperatureChanged = (this.targetTemperature !== temperature)
    let hexData = data[`temperature${temperature}`]

    // You may not want to set the hex data for every single mode...
    if (!hexData) {
      const defaultTemperature = (temperature >= heatTemperature) ? defaultHeatTemperature : defaultCoolTemperature
      hexData = data[`temperature${defaultTemperature}`]

      if (!hexData) {
        const error = Error(`You need to provide a hex code for the given mode/temperature
          ({ "temperature${this.targetTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })
          or at the very least, the default mode/temperature
          ({ "temperature${defaultTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })`)

        log(error.message)

        return callback(error)
      }

      hasTemperatureChanged = (this.targetTemperature !== defaultTemperature)
      this.log(`Update to default temperature (${defaultTemperature})`);

      this.targetTemperature = defaultTemperature
    } else {
      this.targetTemperature = temperature
    }

    if (!hasTemperatureChanged && !this.firstTemperatureUpdate && this.currentHeatingCoolingState !== Characteristic.TargetHeatingCoolingState.OFF) return callback()

    this.firstTemperatureUpdate = false

    const mode = hexData['pseudo-mode']
    this.log(`sendTemperature (${this.targetTemperature}, ${mode})`);
    this.updateServiceHeatingCoolingState(this.heatingCoolingStateForConfigKey(hexData['pseudo-mode']))

    this.thermostatService.setCharacteristic(Characteristic.TargetTemperature, this.targetTemperature);

    this.lastUsedTemperature = this.targetTemperature
    this.lastUsedHeatingCoolingState= this.currentHeatingCoolingState

    sendData(host, hexData.data, callback, log)
  }

  setOnOffState (newState, callback) {
    const { log } = this
    log(`setOnOffState: ${newState}`);

    setTimeout(() => {
      this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey('auto'));
    }, 100)

    callback()
  }

  getOnOffState (callback) {
    this.log(`getSwitchState: ${this.switchState}`);

    callback(null, this.switchState)
  }

	getCurrentHeatingCoolingState (callback) {
		this.log('getCurrentHeatingCoolingState');

    this.updateServiceHeatingCoolingState(this.currentHeatingCoolingState)

    let off = (!this.lastUsedHeatingCoolingState || (this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('off')))

    callback(null, !off);
	}

	getTargetHeatingCoolingState (callback) {
    const currentModeConfigKey = this.configKeyForCurrentHeatingCoolingState()
		this.log(`getTargetHeatingCoolingState (${currentModeConfigKey})`);

		callback(null, this.currentHeatingCoolingState); // success
	}

	setTargetHeatingCoolingState (value, callback) {
    const { config, data, host, log } = this
    const { defaultCoolTemperature, defaultHeatTemperature, replaceAutoMode } = config

    // Perform the auto -> cool/heat conversion descrived in constructor()
    if (replaceAutoMode && this.configKeyForHeatingCoolingState(value) === 'auto') {
      if (this.firstTemperatureUpdate || this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('auto')) {
     		log(`setTargetHeatingCoolingState (converting from auto to ${replaceAutoMode})`);

        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey(replaceAutoMode));
        }, 300)
      } else {
        this.targetTemperature = this.lastUsedTemperature

        log(`setTargetHeatingCoolingState (converting from auto to last used - ${this.configKeyForHeatingCoolingState(this.lastUsedHeatingCoolingState)})`);
        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.lastUsedHeatingCoolingState);
        }, 300)
      }

      return callback()
    }

    if (value === undefined) return callback(); // Some calls are made to this without a value
    if (value === this.currentHeatingCoolingState) return callback()

    let temperature = this.targetTemperature

    if (value === Characteristic.TargetHeatingCoolingState.HEAT) {
      temperature = defaultHeatTemperature
    } else if (value === Characteristic.TargetHeatingCoolingState.COOL) {
      temperature = defaultCoolTemperature
    }

    const currentModeConfigKey = this.configKeyForHeatingCoolingState(value)
    log(`setTargetHeatingCoolingState (${currentModeConfigKey})`);

    if (currentModeConfigKey === 'off') {
      this.updateServiceHeatingCoolingState(value)
      sendData(host, data.off, callback, log)
    } else {
      this.sendTemperature(temperature, callback)
    }
	}

	getCurrentTemperature (callback) {
    const { config, host, log } = this;
    const { minTemperature, pseudoDeviceTemperature } = config

    // Some devices don't include a thermometer
    if (pseudoDeviceTemperature !== undefined) {
      log('getCurrentTemperature (ignored)')
      return callback(null, pseudoDeviceTemperature)
    }

    let device;

    if (host) {
      device = discoveredDevices[host];
    } else {
      const hosts = Object.keys(discoveredDevices)
      if (hosts.length === 0) {
        log(`getCurrentTemperature (no devices found)`);

        return callback(null, 0)
      }

      device = discoveredDevices[hosts[0]];
    }

    if (!device) {
      log(`getCurrentTemperature (no device found at ${host})`);

      return callback(null, 0)
    }

    const callbackIdentifier = Date.now()
    this.callbackQueue[callbackIdentifier] = callback

    // Make sure we're only calling one at a time
    if (Object.keys(this.callbackQueue).length > 1) return

    let onTemperature

    onTemperature = (temperature) => {
      this.currentTemperature = temperature;

      if (this.removeTemperatureListenerTimer) clearTimeout(this.removeTemperatureListenerTimer)
      device.removeListener('temperature', onTemperature);
      this.processQueuedCallbacks()

      log(`getCurrentTemperature (${temperature})`);
 		}

    // Add a 3 second timeout
    this.removeTemperatureListenerTimer = setTimeout(() => {
      device.removeListener('temperature');
      this.processQueuedCallbacks()

   		log(`getCurrentTemperature (3s timeout)`);
    }, 3000)

   	device.on('temperature', onTemperature);

 		device.checkTemperature();
	}

  processQueuedCallbacks () {
    Object.keys(this.callbackQueue).forEach((callbackIdentifier) => {
      const callback = this.callbackQueue[callbackIdentifier]
      callback(null, this.currentTemperature);

      delete this.callbackQueue[callbackIdentifier]
    })
  }

	getTargetTemperature (callback) {
		this.log(`getTargetTemperature (${this.targetTemperature})`);
		callback(null, this.targetTemperature)
	}

	setTargetTemperature (value, callback) {
		this.log(`setTargetTemperature (${value})`);

    this.sendTemperature(value, callback)
	}

	getTemperatureDisplayUnits (callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`getTemperatureDisplayUnits (${temperatureDisplayUnits})`);

		callback(null, temperatureDisplayUnits);
	}

	setTemperatureDisplayUnits (value, callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`setTemperatureDisplayUnits from ${temperatureDisplayUnits} to ${value}`);

		config.temperatureDisplayUnits = value;

		callback(null);
	}

  getName (name, callback) {
		this.log(`getName: ${name}`);

		callback(null, name);
	}

  configKeyForCurrentHeatingCoolingState () {
    return this.configKeyForHeatingCoolingState(this.currentHeatingCoolingState)
  }

  configKeyForHeatingCoolingState (state) {
    switch (state) {
      case Characteristic.TargetHeatingCoolingState.AUTO:
        return 'auto'
      case Characteristic.TargetHeatingCoolingState.COOL:
        return 'cool'
      case Characteristic.TargetHeatingCoolingState.HEAT:
        return 'heat'
      default:
        return 'off'
    }
  }

  heatingCoolingStateForConfigKey (configKey) {
    switch (configKey) {
      case 'off':
        return Characteristic.TargetHeatingCoolingState.OFF
      case 'cool':
        return Characteristic.TargetHeatingCoolingState.COOL
      case 'heat':
        return Characteristic.TargetHeatingCoolingState.HEAT
      default:
        return Characteristic.TargetHeatingCoolingState.AUTO
    }
  }
}

module.exports = AirConAccessory
<commit_msg>Fix aircon default targetTemprature having lower value than minTemperature<commit_after>const BroadlinkRMAccessory = require('./accessory');
const discoveredDevices = require('../helpers/devices.js');
const sendData = require('../helpers/sendData');

class AirConAccessory extends BroadlinkRMAccessory {

  constructor (log, config) {
    super(log, config)

    const { defaultCoolTemperature, defaultHeatTemperature, heatTemperature, minTemperature, maxTemperature, pseudoDeviceTemperature, replaceAutoMode, units } = config

    this.log = log;
    this.currentHeatingCoolingState = Characteristic.CurrentHeatingCoolingState.OFF
    this.targetTemperature = minTemperature
    this.firstTemperatureUpdate = true

    config.minTemperature = minTemperature || 0
    config.maxTemperature = maxTemperature || 30

    config.temperatureDisplayUnits = (units && units.toLowerCase() === 'f') ? Characteristic.TemperatureDisplayUnits.FAHRENHEIT : Characteristic.TemperatureDisplayUnits.CELSIUS;

    // When a temperature hex doesn't exist we try to use the hex set for these
    // default temperatures
    config.defaultCoolTemperature = defaultCoolTemperature || 16
    config.defaultHeatTemperature = defaultHeatTemperature || 30

    // Used to determine when we should use the defaultHeatTemperature or the
    // defaultHeatTemperature
    config.heatTemperature = heatTemperature || 22

    // When we turn on the thermostat with Siri it comes thrugh as "auto" which
    // isn't particularly supported at this time so we convert the mode to cool
    // or heat
    // Note that this is only used when you use Siri or press Auto immediately
    // after launching Homebridge. The rest of the time we'll use your last known
    // temperature
    config.replaceAutoMode = replaceAutoMode || 'cool'

    if (config.pseudoDeviceTemperature > config.minTemperature) throw new Error(`The pseudoDeviceTemperature (${pseudoDeviceTemperature}) must be less than the minTemperature (${minTemperature})`)

    this.callbackQueue = {}
  }

  getServices () {
    const services = super.getServices();
    const { data, config, name } = this;
    const { minTemperature, maxTemperature } = config

    const service = new Service.Thermostat(name);

    // Required Characteristics
    service
      .getCharacteristic(Characteristic.CurrentHeatingCoolingState)
      .on('get', this.getCurrentHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.TargetHeatingCoolingState)
      .on('get', this.getTargetHeatingCoolingState.bind(this))
      .on('set', this.setTargetHeatingCoolingState.bind(this));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .on('get', this.getCurrentTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .on('get', this.getTargetTemperature.bind(this))
      .on('set', this.setTargetTemperature.bind(this));

    service
      .getCharacteristic(Characteristic.TemperatureDisplayUnits)
      .on('get', this.getTemperatureDisplayUnits.bind(this))
      .on('set', this.setTemperatureDisplayUnits.bind(this));

    //
    // service
    //   .getCharacteristic(Characteristic.On)
    //   .on('get', this.getOnOffState.bind(this))
    //   .on('set', this.setOnOffState.bind(this));

    service
      .getCharacteristic(Characteristic.Name)
      .on('get', this.getName.bind(this, name));

    service
      .getCharacteristic(Characteristic.CurrentTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    service
      .getCharacteristic(Characteristic.TargetTemperature)
      .setProps({
        minValue: minTemperature,
        maxValue: maxTemperature,
        minStep: 1
      });

    this.thermostatService = service
    services.push(service)

    return services
  }

  updateServiceHeatingCoolingState (value) {
    if (this.currentHeatingCoolingState === value) return

    this.currentHeatingCoolingState = value

    // this.log(`setCharacteristic for CurrentHeatingCoolingState and TargetHeatingCoolingState (${value})`)
    this.thermostatService.setCharacteristic(Characteristic.CurrentHeatingCoolingState, value);
    this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, value);
  }

  // Thermostat
  sendTemperature (temperature, callback) {
    const { config, data, host, log } = this
    const { defaultHeatTemperature, defaultCoolTemperature, heatTemperature } = config

    log(`Potentially sendTemperature (${temperature})`);

    let hasTemperatureChanged = (this.targetTemperature !== temperature)
    let hexData = data[`temperature${temperature}`]

    // You may not want to set the hex data for every single mode...
    if (!hexData) {
      const defaultTemperature = (temperature >= heatTemperature) ? defaultHeatTemperature : defaultCoolTemperature
      hexData = data[`temperature${defaultTemperature}`]

      if (!hexData) {
        const error = Error(`You need to provide a hex code for the given mode/temperature
          ({ "temperature${this.targetTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })
          or at the very least, the default mode/temperature
          ({ "temperature${defaultTemperature}": { "data": "HEXCODE", "pseudo-mode" : "auto/heat/cool" } })`)

        log(error.message)

        return callback(error)
      }

      hasTemperatureChanged = (this.targetTemperature !== defaultTemperature)
      this.log(`Update to default temperature (${defaultTemperature})`);

      this.targetTemperature = defaultTemperature
    } else {
      this.targetTemperature = temperature
    }

    if (!hasTemperatureChanged && !this.firstTemperatureUpdate && this.currentHeatingCoolingState !== Characteristic.TargetHeatingCoolingState.OFF) return callback()

    this.firstTemperatureUpdate = false

    const mode = hexData['pseudo-mode']
    this.log(`sendTemperature (${this.targetTemperature}, ${mode})`);
    this.updateServiceHeatingCoolingState(this.heatingCoolingStateForConfigKey(hexData['pseudo-mode']))

    this.thermostatService.setCharacteristic(Characteristic.TargetTemperature, this.targetTemperature);

    this.lastUsedTemperature = this.targetTemperature
    this.lastUsedHeatingCoolingState= this.currentHeatingCoolingState

    sendData(host, hexData.data, callback, log)
  }

  setOnOffState (newState, callback) {
    const { log } = this
    log(`setOnOffState: ${newState}`);

    setTimeout(() => {
      this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey('auto'));
    }, 100)

    callback()
  }

  getOnOffState (callback) {
    this.log(`getSwitchState: ${this.switchState}`);

    callback(null, this.switchState)
  }

	getCurrentHeatingCoolingState (callback) {
		this.log('getCurrentHeatingCoolingState');

    this.updateServiceHeatingCoolingState(this.currentHeatingCoolingState)

    let off = (!this.lastUsedHeatingCoolingState || (this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('off')))

    callback(null, !off);
	}

	getTargetHeatingCoolingState (callback) {
    const currentModeConfigKey = this.configKeyForCurrentHeatingCoolingState()
		this.log(`getTargetHeatingCoolingState (${currentModeConfigKey})`);

		callback(null, this.currentHeatingCoolingState); // success
	}

	setTargetHeatingCoolingState (value, callback) {
    const { config, data, host, log } = this
    const { defaultCoolTemperature, defaultHeatTemperature, replaceAutoMode } = config

    // Perform the auto -> cool/heat conversion descrived in constructor()
    if (replaceAutoMode && this.configKeyForHeatingCoolingState(value) === 'auto') {
      if (this.firstTemperatureUpdate || this.lastUsedHeatingCoolingState === this.heatingCoolingStateForConfigKey('auto')) {
     		log(`setTargetHeatingCoolingState (converting from auto to ${replaceAutoMode})`);

        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.heatingCoolingStateForConfigKey(replaceAutoMode));
        }, 300)
      } else {
        this.targetTemperature = this.lastUsedTemperature

        log(`setTargetHeatingCoolingState (converting from auto to last used - ${this.configKeyForHeatingCoolingState(this.lastUsedHeatingCoolingState)})`);
        setTimeout(() => {
          this.thermostatService.setCharacteristic(Characteristic.TargetHeatingCoolingState, this.lastUsedHeatingCoolingState);
        }, 300)
      }

      return callback()
    }

    if (value === undefined) return callback(); // Some calls are made to this without a value
    if (value === this.currentHeatingCoolingState) return callback()

    let temperature = this.targetTemperature

    if (value === Characteristic.TargetHeatingCoolingState.HEAT) {
      temperature = defaultHeatTemperature
    } else if (value === Characteristic.TargetHeatingCoolingState.COOL) {
      temperature = defaultCoolTemperature
    }

    const currentModeConfigKey = this.configKeyForHeatingCoolingState(value)
    log(`setTargetHeatingCoolingState (${currentModeConfigKey})`);

    if (currentModeConfigKey === 'off') {
      this.updateServiceHeatingCoolingState(value)
      sendData(host, data.off, callback, log)
    } else {
      this.sendTemperature(temperature, callback)
    }
	}

	getCurrentTemperature (callback) {
    const { config, host, log } = this;
    const { minTemperature, pseudoDeviceTemperature } = config

    // Some devices don't include a thermometer
    if (pseudoDeviceTemperature !== undefined) {
      log('getCurrentTemperature (ignored)')
      return callback(null, pseudoDeviceTemperature)
    }

    let device;

    if (host) {
      device = discoveredDevices[host];
    } else {
      const hosts = Object.keys(discoveredDevices)
      if (hosts.length === 0) {
        log(`getCurrentTemperature (no devices found)`);

        return callback(null, 0)
      }

      device = discoveredDevices[hosts[0]];
    }

    if (!device) {
      log(`getCurrentTemperature (no device found at ${host})`);

      return callback(null, 0)
    }

    const callbackIdentifier = Date.now()
    this.callbackQueue[callbackIdentifier] = callback

    // Make sure we're only calling one at a time
    if (Object.keys(this.callbackQueue).length > 1) return

    let onTemperature

    onTemperature = (temperature) => {
      this.currentTemperature = temperature;

      if (this.removeTemperatureListenerTimer) clearTimeout(this.removeTemperatureListenerTimer)
      device.removeListener('temperature', onTemperature);
      this.processQueuedCallbacks()

      log(`getCurrentTemperature (${temperature})`);
 		}

    // Add a 3 second timeout
    this.removeTemperatureListenerTimer = setTimeout(() => {
      device.removeListener('temperature');
      this.processQueuedCallbacks()

   		log(`getCurrentTemperature (3s timeout)`);
    }, 3000)

   	device.on('temperature', onTemperature);

 		device.checkTemperature();
	}

  processQueuedCallbacks () {
    Object.keys(this.callbackQueue).forEach((callbackIdentifier) => {
      const callback = this.callbackQueue[callbackIdentifier]
      callback(null, this.currentTemperature);

      delete this.callbackQueue[callbackIdentifier]
    })
  }

	getTargetTemperature (callback) {
		this.log(`getTargetTemperature (${this.targetTemperature})`);
		callback(null, this.targetTemperature)
	}

	setTargetTemperature (value, callback) {
		this.log(`setTargetTemperature (${value})`);

    this.sendTemperature(value, callback)
	}

	getTemperatureDisplayUnits (callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`getTemperatureDisplayUnits (${temperatureDisplayUnits})`);

		callback(null, temperatureDisplayUnits);
	}

	setTemperatureDisplayUnits (value, callback) {
    const { config, log } = this
    const { temperatureDisplayUnits } = config

		log(`setTemperatureDisplayUnits from ${temperatureDisplayUnits} to ${value}`);

		config.temperatureDisplayUnits = value;

		callback(null);
	}

  getName (name, callback) {
		this.log(`getName: ${name}`);

		callback(null, name);
	}

  configKeyForCurrentHeatingCoolingState () {
    return this.configKeyForHeatingCoolingState(this.currentHeatingCoolingState)
  }

  configKeyForHeatingCoolingState (state) {
    switch (state) {
      case Characteristic.TargetHeatingCoolingState.AUTO:
        return 'auto'
      case Characteristic.TargetHeatingCoolingState.COOL:
        return 'cool'
      case Characteristic.TargetHeatingCoolingState.HEAT:
        return 'heat'
      default:
        return 'off'
    }
  }

  heatingCoolingStateForConfigKey (configKey) {
    switch (configKey) {
      case 'off':
        return Characteristic.TargetHeatingCoolingState.OFF
      case 'cool':
        return Characteristic.TargetHeatingCoolingState.COOL
      case 'heat':
        return Characteristic.TargetHeatingCoolingState.HEAT
      default:
        return Characteristic.TargetHeatingCoolingState.AUTO
    }
  }
}

module.exports = AirConAccessory
<|endoftext|>


<commit_before>/**
 * GravityTester Class
 *
 * Class to handle gravity cases.
 * Gets positions, does viewport tests.
 */

(function (root, factory) {

	'use strict';

	if (typeof define === 'function' && define.amd) {
		define([
			'jquery',
			'./Constants',
			'./GravityParser',
			'./PositionCalculator'
		], factory);
	} else if (typeof exports === 'object') {
		module.exports = factory(
			require('jquery'),
			require('./Constants'),
			require('./GravityParser'),
			require('./PositionCalculator')
		);
	} else {
		root.ProtipGravityTester = factory(
			root.jQuery,
			root.ProtipConstants,
			root.ProtipGravityParser,
			root.ProtipPositionCalculator
		);
	}
}(this, function ($, C, GravityParser, PositionCalculator) {

	'use strict';

	/**
	 * GravityTester class.
	 *
	 * @param protipItem {ProtipItemClass} The ProtipItem Instance.
	 * @returns {<top>:<string>, <left>:<string>}
	 * @constructor
	 */
	var GravityTester = function(protipItem){
		return this._Construct(protipItem);
	};

	// Define the GravityTester members.
	$.extend(true, GravityTester.prototype, {

		/**
		 * Constructor.
		 *
		 * @memberOf GravityTester
		 * @param protipItemInstance {ProtipItemClass} The ProtipItem Instance.
		 * @returns {<top>:<string>, <left>:<string>}
		 * @private
		 */
		_Construct: function(protipItemInstance){
			/**
			 * ItemClass instance.
			 *
			 * @type {ProtipItemClass}
			 * @private
			 */
			this._item = protipItemInstance;

			/**
			 * Results.
			 *
			 * @type {object}
			 * @private
			 */
			this._result = undefined;

			// Set some initial values.
			this._setWindowDimensions();

			/**
			 * List of positions to check.
			 *
			 * @type {array}
			 * @private
			 */
			this._positionList = new GravityParser(this._item.data.gravity, this._item.data.position);

			// Iterate through each position and do a check.
			var i;
			for (i = 0; i < this._positionList.length; i++) {
				// We had a successful test, break the loop.
				if (this._test(this._positionList[i])){
					break;
				}
			}

			// Return the result if we had one. Return values for the default position if not.
			return this._result || new PositionCalculator(this._item);
		},

		/**
		 * Does position test based on the position given in the parameter.
		 * It'll do a position calculation and tries the protip from every side.
		 *
		 * @param position {object} Position of the element.
		 * @returns {boolean}
		 * @private
		 */
		_test: function(position){
			var result = new PositionCalculator(this._item, position.key, position);
			this._item.el.protip.css(result);
			this._setProtipDimensions();

			if (this._topOk() && this._rightOk() && this._bottomOk() && this._leftOk()) {
				result.position = position.key;
				this._result = result;
				return true;
			}
			return false;
		},

		/**
		 * Check protip overflow from the top.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_topOk: function(){
			return ((this._dimensions.offset.top - this._windowDimensions.scrollTop) > 0);
		},

		/**
		 * Check protip overflow from the right.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_rightOk: function(){
			return ((this._dimensions.offset.left + this._dimensions.width) < this._windowDimensions.width);
		},

		/**
		 * Check protip overflow from the bottom
		 *
		 * @returns {boolean}
		 * @private
		 */
		_bottomOk: function(){
			return (((this._dimensions.offset.top - this._windowDimensions.scrollTop) + this._dimensions.height) < this._windowDimensions.height);
		},

		/**
		 * Check protip overflow from the left
		 *
		 * @returns {boolean}
		 * @private
		 */
		_leftOk: function(){
			return (this._dimensions.offset.left > 0);
		},

		/**
		 * Gets/sets initial protip dimensions to caclulate with.
		 *
		 * @private
		 */
		_setProtipDimensions: function(){
			if (this._item.classInstance.settings.forceMinWidth) {
				this._item.el.protip.css({
					position: 'fixed',
					left: 0,
					top: 0,
					minWidth: 0
				});

				var minWidth = this._item.el.protip.width();
				this._item.el.protip.css({
					position: '',
					left: '',
					top: '',
					minWidth: minWidth + 'px'
				});
			}

			this._dimensions = {
				width:  this._item.el.protip.outerWidth(),
				height: this._item.el.protip.outerHeight(),
				offset: this._item.el.protip.offset()
			};
		},

		/**
		 * Get some window dimension values
		 *
		 * @private
		 */
		_setWindowDimensions: function(){
			var w = window,
				d = document,
				e = d.documentElement,
				g = d.getElementsByTagName('body')[0],
				x = w.innerWidth || e.clientWidth || g.clientWidth,
				y = w.innerHeight|| e.clientHeight|| g.clientHeight;

			this._windowDimensions = {
				width: parseInt(x),
				height: parseInt(y),
				scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.getElementsByTagName('body')[0].scrollTop || 0)
			};
		}
	});

	return GravityTester;

}));<commit_msg>Min-width calculation bugfix (Firefox)<commit_after>/**
 * GravityTester Class
 *
 * Class to handle gravity cases.
 * Gets positions, does viewport tests.
 */

(function (root, factory) {

	'use strict';

	if (typeof define === 'function' && define.amd) {
		define([
			'jquery',
			'./Constants',
			'./GravityParser',
			'./PositionCalculator'
		], factory);
	} else if (typeof exports === 'object') {
		module.exports = factory(
			require('jquery'),
			require('./Constants'),
			require('./GravityParser'),
			require('./PositionCalculator')
		);
	} else {
		root.ProtipGravityTester = factory(
			root.jQuery,
			root.ProtipConstants,
			root.ProtipGravityParser,
			root.ProtipPositionCalculator
		);
	}
}(this, function ($, C, GravityParser, PositionCalculator) {

	'use strict';

	/**
	 * GravityTester class.
	 *
	 * @param protipItem {ProtipItemClass} The ProtipItem Instance.
	 * @returns {<top>:<string>, <left>:<string>}
	 * @constructor
	 */
	var GravityTester = function(protipItem){
		return this._Construct(protipItem);
	};

	// Define the GravityTester members.
	$.extend(true, GravityTester.prototype, {

		/**
		 * Constructor.
		 *
		 * @memberOf GravityTester
		 * @param protipItemInstance {ProtipItemClass} The ProtipItem Instance.
		 * @returns {<top>:<string>, <left>:<string>}
		 * @private
		 */
		_Construct: function(protipItemInstance){
			/**
			 * ItemClass instance.
			 *
			 * @type {ProtipItemClass}
			 * @private
			 */
			this._item = protipItemInstance;

			/**
			 * Results.
			 *
			 * @type {object}
			 * @private
			 */
			this._result = undefined;

			// Set some initial values.
			this._setWindowDimensions();

			/**
			 * List of positions to check.
			 *
			 * @type {array}
			 * @private
			 */
			this._positionList = new GravityParser(this._item.data.gravity, this._item.data.position);

			// Iterate through each position and do a check.
			var i;
			for (i = 0; i < this._positionList.length; i++) {
				// We had a successful test, break the loop.
				if (this._test(this._positionList[i])){
					break;
				}
			}

			// Return the result if we had one. Return values for the default position if not.
			return this._result || new PositionCalculator(this._item);
		},

		/**
		 * Does position test based on the position given in the parameter.
		 * It'll do a position calculation and tries the protip from every side.
		 *
		 * @param position {object} Position of the element.
		 * @returns {boolean}
		 * @private
		 */
		_test: function(position){
			var result = new PositionCalculator(this._item, position.key, position);
			this._item.el.protip.css(result);
			this._setProtipDimensions();

			if (this._topOk() && this._rightOk() && this._bottomOk() && this._leftOk()) {
				result.position = position.key;
				this._result = result;
				return true;
			}
			return false;
		},

		/**
		 * Check protip overflow from the top.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_topOk: function(){
			return ((this._dimensions.offset.top - this._windowDimensions.scrollTop) > 0);
		},

		/**
		 * Check protip overflow from the right.
		 *
		 * @returns {boolean}
		 * @private
		 */
		_rightOk: function(){
			return ((this._dimensions.offset.left + this._dimensions.width) < this._windowDimensions.width);
		},

		/**
		 * Check protip overflow from the bottom
		 *
		 * @returns {boolean}
		 * @private
		 */
		_bottomOk: function(){
			return (((this._dimensions.offset.top - this._windowDimensions.scrollTop) + this._dimensions.height) < this._windowDimensions.height);
		},

		/**
		 * Check protip overflow from the left
		 *
		 * @returns {boolean}
		 * @private
		 */
		_leftOk: function(){
			return (this._dimensions.offset.left > 0);
		},

		/**
		 * Gets/sets initial protip dimensions to caclulate with.
		 *
		 * @private
		 */
		_setProtipDimensions: function(){
			if (this._item.classInstance.settings.forceMinWidth) {
				this._item.el.protip.css({
					position: 'fixed',
					left: 0,
					top: 0,
					minWidth: 0
				});

				var minWidth = this._item.el.protip.width() + 1; // Thanks Firefox
				this._item.el.protip.css({
					position: '',
					left: '',
					top: '',
					minWidth: minWidth + 'px'
				});
			}

			this._dimensions = {
				width:  this._item.el.protip.outerWidth(),
				height: this._item.el.protip.outerHeight(),
				offset: this._item.el.protip.offset()
			};
		},

		/**
		 * Get some window dimension values
		 *
		 * @private
		 */
		_setWindowDimensions: function(){
			var w = window,
				d = document,
				e = d.documentElement,
				g = d.getElementsByTagName('body')[0],
				x = w.innerWidth || e.clientWidth || g.clientWidth,
				y = w.innerHeight|| e.clientHeight|| g.clientHeight;

			this._windowDimensions = {
				width: parseInt(x),
				height: parseInt(y),
				scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.getElementsByTagName('body')[0].scrollTop || 0)
			};
		}
	});

	return GravityTester;

}));<|endoftext|>


<commit_before>/**
 * Created by Edi on 26-Apr-17.
 */
var app = app || {};

app.scheduleViewBag = (function() {
    function showSchedule(data) {
        var scheduleDays = $('#schedule-table .schedule-day');
        scheduleDays.each(function (index, scheduleDay) {
            appendAppointmentTimes(scheduleDay, data.editDayScheduleModels[index], data.appointmentDuration);
        });
    }

    function appendAppointmentTimes(scheduleDay, dayScheduleData, appointmentDuration) {
        var startTime = dayScheduleData.startTimeStr.substr(0, 5).split(':');
        var endTime = dayScheduleData.endTimeStr.substr(0, 5).split(':');
        var startTimeMinutes = Number(startTime[0]) * 60 + Number(startTime[1]);
        var endTimeMinutes = Number(endTime[0]) * 60 + Number(endTime[1]);

        var currentAppointmentTime = startTimeMinutes;
        while(currentAppointmentTime < endTimeMinutes) {
            var hours = Math.floor(currentAppointmentTime / 60);
            var minutes = currentAppointmentTime % 60;
            var time = hours + ':' + minutes;

            var appointmentEl = $('<div>');
            var appointmentLink = $('<a>').text(time)
                .attr('href', '/appointment/add?date=' + new Date().toLocaleString("en-US").replace(',', ''));

            $(appointmentEl).append(appointmentLink);
            $(scheduleDay).append(appointmentEl);

            currentAppointmentTime += appointmentDuration;
        }
    }

    return {
        load: function () {
            return {
                showSchedule: showSchedule
            }
        }
    }
}());<commit_msg>appointment dates fix<commit_after>/**
 * Created by Edi on 26-Apr-17.
 */
var app = app || {};

app.scheduleViewBag = (function () {
    function showSchedule(data) {
        var scheduleDays = $('#schedule-table .schedule-day');
        scheduleDays.each(function (index, scheduleDay) {
            appendAppointmentTimes(scheduleDay, data.editDayScheduleModels[index], data.appointmentDuration);
        });
    }

    function appendAppointmentTimes(scheduleDay, dayScheduleData, appointmentDuration) {
        var startTime = dayScheduleData.startTimeStr.substr(0, 5).split(':');
        var endTime = dayScheduleData.endTimeStr.substr(0, 5).split(':');
        var startTimeMinutes = Number(startTime[0]) * 60 + Number(startTime[1]);
        var endTimeMinutes = Number(endTime[0]) * 60 + Number(endTime[1]);

        var currentAppointmentTime = startTimeMinutes;
        var index = 0;
        while (currentAppointmentTime < endTimeMinutes) {
            var hours = Math.floor(currentAppointmentTime / 60);
            var minutes = currentAppointmentTime % 60;
            var time = ('0' + hours).slice(-2) + ':' +
                ('0' + minutes).slice(-2);

            var appointmentEl = $('<span>');
            var appointmentLink = $('<a>').text(time)
                .attr('href', '/appointment/add?date=' + new Date().toLocaleString("en-US").replace(',', ''));

            $(appointmentEl).append(appointmentLink);
            $(scheduleDay).append(appointmentEl);

            currentAppointmentTime += appointmentDuration;

            if (index % 2 == 0) {
                $(scheduleDay).append(' ');
            } else {
                $(scheduleDay).append($('<br>'));
            }
            ++index;
        }
    }

    return {
        load: function () {
            return {
                showSchedule: showSchedule
            }
        }
    }
}());<|endoftext|>


<commit_before>export class Pos {
  constructor(path, offset) {
    this.path = path
    this.offset = offset
  }

  toString() {
    return this.path.join("/") + ":" + this.offset
  }

  get depth() {
    return this.path.length
  }

  static cmp(pathA, offsetA, pathB, offsetB) {
    let lenA = pathA.length, lenB = pathB.length
    for (var i = 0, end = Math.min(lenA, lenB); i < end; i++) {
      var diff = pathA[i] - pathB[i]
      if (diff != 0) return diff
    }
    if (lenA > lenB)
      return offsetB <= pathA[i] ? 1 : -1
    else if (lenB > lenA)
      return offsetA <= pathB[i] ? -1 : 1
    else
      return offsetA - offsetB
  }

  static samePath(pathA, pathB) {
    if (pathA.length != pathB.length) return false
    for (let i = 0; i < pathA.length; i++) if (pathA[i] !== pathB[i]) return false
    return true
  }

  cmp(other) {
    if (other == this) return 0
    return Pos.cmp(this.path, this.offset, other.path, other.offset)
  }

  static shorten(path, to = null, offset = 0) {
    if (to == null) to = path.length - 1
    return new Pos(path.slice(0, to), path[to] + offset)
  }

  shorten(to = null, offset = 0) {
    if (to >= this.depth) return this
    return Pos.shorten(this.path, to, offset)
  }

  move(by) {
    return new Pos(this.path, this.offset + by)
  }

  toPath(offset = 0) {
    return this.path.concat(this.offset + offset)
  }

  extend(pos) {
    let path = this.path.slice(), add = this.offset
    for (let i = 0; i < pos.path.length; i++) {
      path.push(pos.path[i] + add)
      add = 0
    }
    return new Pos(path, pos.offset + add)
  }

  toJSON() { return this }

  static from(array, extraOffset = 0) {
    if (!array.length) throw new Error("Can't create a pos from an empty array")
    return new Pos(array.slice(0, array.length - 1), array[array.length - 1] + extraOffset)
  }

  static fromJSON(json) { return new Pos(json.path, json.offset) }

  static after(node, pos) { return findAfter(node, pos, []) }
  static start(node, path) { return findLeft(node, path ? path.slice() : []) }

  static before(node, pos) { return findBefore(node, pos, []) }
  static end(node, path) { return findRight(node, path ? path.slice() : []) }

  static near(node, pos, bias) {
    return (bias > 0 && Pos.after(node, pos)) || Pos.before(node, pos) || Pos.after(node, pos)
  }
}

function findLeft(node, path) {
  if (node.isTextblock)
    return new Pos(path, 0)
  for (let i = 0; i < node.length; i++) {
    path.push(i)
    let found = findLeft(node.child(i), path)
    if (found) return found
    path.pop()
  }
}

function findAfter(node, pos, path) {
  if (node.isTextblock)
    return pos
  let atEnd = path.length == pos.path.length
  let start = atEnd ? pos.offset : pos.path[path.length]
  for (let i = start; i < node.length; i++) {
    path.push(i)
    let child = node.child(i)
    let found = i == start && !atEnd ? findAfter(child, pos, path) : findLeft(child, path)
    if (found) return found
    path.pop()
  }
}

function findRight(node, path) {
  if (node.isTextblock)
    return new Pos(path, node.maxOffset)
  for (let i = node.length - 1; i >= 0; i--) {
    path.push(i)
    let found = findRight(node.child(i), path)
    if (found) return found
    path.pop()
  }
}

function findBefore(node, pos, path) {
  if (node.isTextblock) return pos
  let atEnd = pos.path.length == path.length
  let end = atEnd ? pos.offset - 1 : pos.path[path.length]
  for (let i = end; i >= 0; i--) {
    path.push(i)
    let child = node.child(i)
    let found = i == end && !atEnd ? findBefore(child, pos, path) : findRight(child, path)
    if (found) return found
    path.pop()
  }
}
<commit_msg>Finally remove Pos.near and co<commit_after>export class Pos {
  constructor(path, offset) {
    this.path = path
    this.offset = offset
  }

  toString() {
    return this.path.join("/") + ":" + this.offset
  }

  get depth() {
    return this.path.length
  }

  static cmp(pathA, offsetA, pathB, offsetB) {
    let lenA = pathA.length, lenB = pathB.length
    for (var i = 0, end = Math.min(lenA, lenB); i < end; i++) {
      var diff = pathA[i] - pathB[i]
      if (diff != 0) return diff
    }
    if (lenA > lenB)
      return offsetB <= pathA[i] ? 1 : -1
    else if (lenB > lenA)
      return offsetA <= pathB[i] ? -1 : 1
    else
      return offsetA - offsetB
  }

  static samePath(pathA, pathB) {
    if (pathA.length != pathB.length) return false
    for (let i = 0; i < pathA.length; i++) if (pathA[i] !== pathB[i]) return false
    return true
  }

  cmp(other) {
    if (other == this) return 0
    return Pos.cmp(this.path, this.offset, other.path, other.offset)
  }

  static shorten(path, to = null, offset = 0) {
    if (to == null) to = path.length - 1
    return new Pos(path.slice(0, to), path[to] + offset)
  }

  shorten(to = null, offset = 0) {
    if (to >= this.depth) return this
    return Pos.shorten(this.path, to, offset)
  }

  move(by) {
    return new Pos(this.path, this.offset + by)
  }

  toPath(offset = 0) {
    return this.path.concat(this.offset + offset)
  }

  extend(pos) {
    let path = this.path.slice(), add = this.offset
    for (let i = 0; i < pos.path.length; i++) {
      path.push(pos.path[i] + add)
      add = 0
    }
    return new Pos(path, pos.offset + add)
  }

  toJSON() { return this }

  static from(array, extraOffset = 0) {
    if (!array.length) throw new Error("Can't create a pos from an empty array")
    return new Pos(array.slice(0, array.length - 1), array[array.length - 1] + extraOffset)
  }

  static fromJSON(json) { return new Pos(json.path, json.offset) }
}
<|endoftext|>