<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var d3 = require('../lib/').d3;
var _ = require('../lib/')._;

var Duration = require('../lib/').Duration;
var format = require('../data/util/format');
var log = require('../lib/').bows('Bolus');

module.exports = function(pool, opts) {

  var QUARTER = ' ¼', HALF = ' ½', THREE_QUARTER = ' ¾', THIRD = ' ⅓', TWO_THIRDS = ' ⅔';

  var MS_IN_ONE = 60000;

  opts = opts || {};

  var defaults = {
    classes: {
      'unspecial': {'tooltip': 'tooltip_bolus_small.svg', 'width': 70, 'height': 24},
      'two-line': {'tooltip': 'tooltip_bolus_large.svg', 'width': 98, 'height': 39},
      'three-line': {'tooltip': 'tooltip_bolus_extralarge.svg', 'width': 98, 'height': 58}
    },
    width: 12,
    bolusStroke: 2,
    triangleSize: 6,
    carbTooltipCatcher: 5
  };

  _.defaults(opts, defaults);

  var carbTooltipBuffer = opts.carbTooltipCatcher * MS_IN_ONE;

  // catch bolus tooltips events
  opts.emitter.on('carbTooltipOn', function(t) {
    var b = _.find(opts.data, function(d) {
      var bolusT = Date.parse(d.normalTime);
      if (bolusT >= (t - carbTooltipBuffer) && (bolusT <= (t + carbTooltipBuffer))) {
        return d;
      }
    });
    if (b) {
      bolus.addTooltip(b, bolus.getTooltipCategory(b));
      opts.emitter.emit('noCarbTimestamp', true);
    }
  });
  opts.emitter.on('carbTooltipOff', function(t) {
    var b = _.find(opts.data, function(d) {
      var bolusT = Date.parse(d.normalTime);
      if (bolusT >= (t - carbTooltipBuffer) && (bolusT <= (t + carbTooltipBuffer))) {
        return d;
      }
    });
    if (b) {
      d3.select('#tooltip_' + b._id).remove();
      opts.emitter.emit('noCarbTimestamp', false);
    }
  });

  function unknownDeliverySplit(d) {
    return d.initialDelivery == null && d.extendedDelivery == null;
  }

  function computePathHeight(d) {
    if (unknownDeliverySplit(d)) {
      return opts.yScale(d.value) + opts.bolusStroke / 2;
    } else {
      return opts.yScale(d.extendedDelivery) + opts.bolusStroke / 2;
    }
  }

  function bolus(selection) {
    opts.xScale = pool.xScale().copy();
    selection.each(function(currentData) {
      var boluses = d3.select(this)
        .selectAll('g')
        .data(currentData, function(d) {
          return d._id;
        });
      var bolusGroups = boluses.enter()
        .append('g')
        .attr({
          'class': 'd3-bolus-group'
        });
      var top = opts.yScale.range()[0];
      // boluses where delivered = recommended
      bolusGroups.append('rect')
        .attr({
          'x': function(d) {
            return bolus.x(d);
          },
          'y': function(d) {
            return opts.yScale(d.value);
          },
          'width': opts.width,
          'height': function(d) {
            return top - opts.yScale(d.value);
          },
          'class': 'd3-rect-bolus d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      // boluses where recommendation and delivery differ
      var bottom = top - opts.bolusStroke / 2;
      // boluses where recommended > delivered
      var underride = bolusGroups.filter(function(d) {
        if (d.recommended > d.value) {
          return d;
        }
      });
      underride.append('rect')
        .attr({
          'x': function(d) {
            return bolus.x(d);
          },
          'y': function(d) {
            return opts.yScale(d.recommended);
          },
          'width': opts.width,
          'height': function(d) {
            return opts.yScale(d.value) - opts.yScale(d.recommended);
          },
          'class': 'd3-rect-recommended d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      // boluses where delivered > recommended
      var override = bolusGroups.filter(function(d) {
        if (d.value > d.recommended) {
          return d;
        }
      });
      override.append('rect')
        .attr({
          'x': function(d) {
            return bolus.x(d);
          },
          'y': function(d) {
            return opts.yScale(d.recommended);
          },
          'width': opts.width,
          'height': function(d) {
            return top - opts.yScale(d.recommended);
          },
          'stroke-width': opts.bolusStroke,
          'class': 'd3-rect-recommended d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      override.append('path')
        .attr({
          'd': function(d) {
            var leftEdge = bolus.x(d) + opts.bolusStroke / 2;
            var rightEdge = leftEdge + opts.width - opts.bolusStroke;
            var bolusHeight = opts.yScale(d.value) + opts.bolusStroke / 2;
            return 'M' + leftEdge + ' ' + bottom + 'L' + rightEdge + ' ' + bottom + 'L' + rightEdge + ' ' + bolusHeight + 'L' + leftEdge + ' ' + bolusHeight + 'Z';
          },
          'stroke-width': opts.bolusStroke,
          'class': 'd3-path-bolus d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });

      // square- and dual-wave boluses
      var extendedBoluses = bolusGroups.filter(function(d) {
        if (d.extended) {
          return d;
        }
      });
      extendedBoluses.append('path')
        .attr({
          'd': function(d) {
            var rightEdge = bolus.x(d) + opts.width;
            var doseHeight = computePathHeight(d);
            var doseEnd = opts.xScale(Date.parse(d.normalTime) + d.duration) - opts.triangleSize / 2;
            return 'M' + rightEdge + ' ' + doseHeight + 'L' + doseEnd + ' ' + doseHeight;
          },
          'stroke-width': opts.bolusStroke,
          'class': function(d){
            if (unknownDeliverySplit(d)) {
              return 'd3-path-extended d3-bolus d3-unknown-delivery-split';
            } else {
              return 'd3-path-extended d3-bolus';
            }
          },
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      extendedBoluses.append('path')
        .attr({
          'd': function(d) {
            var doseHeight = computePathHeight(d);
            var doseEnd = opts.xScale(Date.parse(d.normalTime) + d.duration) - opts.triangleSize;
            return bolus.triangle(doseEnd, doseHeight);
          },
          'stroke-width': opts.bolusStroke,
          'class': 'd3-path-extended-triangle d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      boluses.exit().remove();

      // tooltips
      d3.selectAll('.d3-rect-bolus, .d3-rect-recommended').on('mouseover', function(d) {
        bolus.addTooltip(d, bolus.getTooltipCategory(d));
        opts.emitter.emit('bolusTooltipOn', Date.parse(d.normalTime));
      });
      d3.selectAll('.d3-rect-bolus, .d3-rect-recommended').on('mouseout', function(d) {
        d3.select('#tooltip_' + d._id).remove();
        opts.emitter.emit('bolusTooltipOff', Date.parse(d.normalTime));
      });
    });
  }

  function formatValue(x) {
    var formatted = d3.format('.3f')(x);
    // remove zero-padding on the right
    while (formatted[formatted.length - 1] === '0') {
      formatted = formatted.slice(0, formatted.length - 1);
    }
    if (formatted[formatted.length - 1] === '.') {
      formatted = formatted + '0';
    }
    return formatted;
  }

  function getRecommendedBolusTooltipText(d) {
    return function(){
      return formatValue(d.recommended) + "U recom'd";
    };
  }

  function getExtendedBolusTooltipText(d) {
    return function() {
      if (unknownDeliverySplit(d)) {
        return 'Split unknown';
      }
      return format.percentage(d.extendedDelivery / d.value) + ' ' + bolus.timespan(d);
    };
  }

  bolus.getTooltipCategory = function(d) {
    var category;
    // when there's no 'recommended' field
    if (d.recommended == null) {
      if (d.extended == null) {
        category = 'unspecial';
      } else {
        category = 'two-line';
      }
    } else {
      if ((d.extended == null) && (d.recommended === d.value)) {
        category = 'unspecial';
      } else if ((d.extended == null) && (d.recommended !== d.value)) {
        category = 'two-line';
      } else if ((d.recommended === d.value) && (d.extended != null)) {
        category = 'two-line';
      } else if ((d.recommended !== d.value) && (d.extended != null)) {
        category = 'three-line';
      }
    }
    return category;
  };

  bolus.addTooltip = function(d, category) {
    var tooltipWidth = opts.classes[category].width;
    var tooltipHeight = opts.classes[category].height;

    d3.select('#' + 'tidelineTooltips_bolus')
      .call(pool.tooltips(),
        d,
        // tooltipXPos
        opts.xScale(Date.parse(d.normalTime)),
        'bolus',
        // timestamp
        true,
        opts.classes[category].tooltip,
        tooltipWidth,
        tooltipHeight,
        // imageX
        opts.xScale(Date.parse(d.normalTime)),
        // imageY
        function() {
          return pool.height() - tooltipHeight;
        },
        // textX
        opts.xScale(Date.parse(d.normalTime)) + tooltipWidth / 2,
        // textY
        function() {
          if (category === 'unspecial') {
            return pool.height() - tooltipHeight * (9/16);
          } else if (category === 'two-line') {
            return pool.height() - tooltipHeight * (3/4);
          } else if (category === 'three-line') {
            return pool.height() - tooltipHeight * (13/16);
          } else {
            return pool.height() - tooltipHeight;
          }

        },
        // customText
        (function() {
          return formatValue(d.value) + 'U';
        }()),
        // tspan
        (function() {
          if (d.extended) {
            return ' total';
          }
        }())
      );

    if (category === 'two-line') {
      var twoLineSelection = d3.select('#tooltip_' + d._id).select('.d3-tooltip-text-group').append('text')
        .attr({
          'class': 'd3-tooltip-text d3-bolus',
          'x': opts.xScale(Date.parse(d.normalTime)) + tooltipWidth / 2,
          'y': pool.height() - tooltipHeight / 3
        })
        .append('tspan');

      if ((d.recommended != null) && (d.recommended !== d.value)) {
        twoLineSelection.text(getRecommendedBolusTooltipText(d));
      }
      else if (d.extended != null) {
        twoLineSelection.text(getExtendedBolusTooltipText(d));
      }

      twoLineSelection.attr('class', 'd3-bolus');
    } else if (category === 'three-line') {
      d3.select('#tooltip_' + d._id).select('.d3-tooltip-text-group').append('text')
        .attr({
          'class': 'd3-tooltip-text d3-bolus',
          'x': opts.xScale(Date.parse(d.normalTime)) + tooltipWidth / 2,
          'y': pool.height() - tooltipHeight / 2
        })
        .append('tspan')
        .text(getRecommendedBolusTooltipText(d))
        .attr('class', 'd3-bolus');

      d3.select('#tooltip_' + d._id).select('.d3-tooltip-text-group').append('text')
        .attr({
          'class': 'd3-tooltip-text d3-bolus',
          'x': opts.xScale(Date.parse(d.normalTime)) + tooltipWidth / 2,
          'y': pool.height() - tooltipHeight / 4
        })
        .append('tspan')
        .text(getExtendedBolusTooltipText(d))
        .attr('class', 'd3-bolus');
    }
  };

  bolus.timespan = function(d) {
    var dur = Duration.parse(d.duration + 'ms');
    var hours = dur.hours();
    var minutes = dur.minutes() - (hours * 60);
    if (hours !== 0) {
      if (hours === 1) {
        switch(minutes) {
        case 0:
          return 'over ' + hours + ' hr';
        case 15:
          return 'over ' + hours + QUARTER + ' hr';
        case 20:
          return 'over ' + hours + THIRD + ' hr';
        case 30:
          return 'over ' + hours + HALF + ' hr';
        case 40:
          return 'over ' + hours + TWO_THIRDS + ' hr';
        case 45:
          return 'over ' + hours + THREE_QUARTER + ' hr';
        default:
          return 'over ' + hours + ' hr ' + minutes + ' min';
        }
      } else {
        switch(minutes) {
        case 0:
          return 'over ' + hours + ' hrs';
        case 15:
          return 'over ' + hours + QUARTER + ' hrs';
        case 20:
          return 'over ' + hours + THIRD + ' hrs';
        case 30:
          return 'over ' + hours + HALF + ' hrs';
        case 40:
          return 'over ' + hours + TWO_THIRDS + ' hrs';
        case 45:
          return 'over ' + hours + THREE_QUARTER + ' hrs';
        default:
          return 'over ' + hours + ' hrs ' + minutes + ' min';
        }
      }
    } else {
      return 'over ' + minutes + ' min';
    }
  };

  bolus.x = function(d) {
    return opts.xScale(Date.parse(d.normalTime)) - opts.width/2;
  };

  bolus.triangle = function(x, y) {
    var top = (x + opts.triangleSize) + ' ' + (y + opts.triangleSize/2);
    var bottom = (x + opts.triangleSize) + ' ' + (y - opts.triangleSize/2);
    var point = x + ' ' + y;
    return 'M' + top + 'L' + bottom + 'L' + point + 'Z';
  };

  return bolus;
};
<commit_msg>adjust tooltip text getters; use 'datum' everywhere instead of 'd' when not a proper D3 selection to avoid confusion<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var d3 = require('../lib/').d3;
var _ = require('../lib/')._;

var Duration = require('../lib/').Duration;
var format = require('../data/util/format');
var log = require('../lib/').bows('Bolus');

module.exports = function(pool, opts) {

  var QUARTER = ' ¼', HALF = ' ½', THREE_QUARTER = ' ¾', THIRD = ' ⅓', TWO_THIRDS = ' ⅔';

  var MS_IN_ONE = 60000;

  opts = opts || {};

  var defaults = {
    classes: {
      'unspecial': {'tooltip': 'tooltip_bolus_small.svg', 'width': 70, 'height': 24},
      'two-line': {'tooltip': 'tooltip_bolus_large.svg', 'width': 98, 'height': 39},
      'three-line': {'tooltip': 'tooltip_bolus_extralarge.svg', 'width': 98, 'height': 58}
    },
    width: 12,
    bolusStroke: 2,
    triangleSize: 6,
    carbTooltipCatcher: 5
  };

  _.defaults(opts, defaults);

  var carbTooltipBuffer = opts.carbTooltipCatcher * MS_IN_ONE;

  // catch bolus tooltips events
  opts.emitter.on('carbTooltipOn', function(t) {
    var b = _.find(opts.data, function(d) {
      var bolusT = Date.parse(d.normalTime);
      if (bolusT >= (t - carbTooltipBuffer) && (bolusT <= (t + carbTooltipBuffer))) {
        return d;
      }
    });
    if (b) {
      bolus.addTooltip(b, bolus.getTooltipCategory(b));
      opts.emitter.emit('noCarbTimestamp', true);
    }
  });
  opts.emitter.on('carbTooltipOff', function(t) {
    var b = _.find(opts.data, function(d) {
      var bolusT = Date.parse(d.normalTime);
      if (bolusT >= (t - carbTooltipBuffer) && (bolusT <= (t + carbTooltipBuffer))) {
        return d;
      }
    });
    if (b) {
      d3.select('#tooltip_' + b._id).remove();
      opts.emitter.emit('noCarbTimestamp', false);
    }
  });

  function unknownDeliverySplit(d) {
    return d.initialDelivery == null && d.extendedDelivery == null;
  }

  function computePathHeight(d) {
    if (unknownDeliverySplit(d)) {
      return opts.yScale(d.value) + opts.bolusStroke / 2;
    } else {
      return opts.yScale(d.extendedDelivery) + opts.bolusStroke / 2;
    }
  }

  function bolus(selection) {
    opts.xScale = pool.xScale().copy();
    selection.each(function(currentData) {
      var boluses = d3.select(this)
        .selectAll('g')
        .data(currentData, function(d) {
          return d._id;
        });
      var bolusGroups = boluses.enter()
        .append('g')
        .attr({
          'class': 'd3-bolus-group'
        });
      var top = opts.yScale.range()[0];
      // boluses where delivered = recommended
      bolusGroups.append('rect')
        .attr({
          'x': function(d) {
            return bolus.x(d);
          },
          'y': function(d) {
            return opts.yScale(d.value);
          },
          'width': opts.width,
          'height': function(d) {
            return top - opts.yScale(d.value);
          },
          'class': 'd3-rect-bolus d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      // boluses where recommendation and delivery differ
      var bottom = top - opts.bolusStroke / 2;
      // boluses where recommended > delivered
      var underride = bolusGroups.filter(function(d) {
        if (d.recommended > d.value) {
          return d;
        }
      });
      underride.append('rect')
        .attr({
          'x': function(d) {
            return bolus.x(d);
          },
          'y': function(d) {
            return opts.yScale(d.recommended);
          },
          'width': opts.width,
          'height': function(d) {
            return opts.yScale(d.value) - opts.yScale(d.recommended);
          },
          'class': 'd3-rect-recommended d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      // boluses where delivered > recommended
      var override = bolusGroups.filter(function(d) {
        if (d.value > d.recommended) {
          return d;
        }
      });
      override.append('rect')
        .attr({
          'x': function(d) {
            return bolus.x(d);
          },
          'y': function(d) {
            return opts.yScale(d.recommended);
          },
          'width': opts.width,
          'height': function(d) {
            return top - opts.yScale(d.recommended);
          },
          'stroke-width': opts.bolusStroke,
          'class': 'd3-rect-recommended d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      override.append('path')
        .attr({
          'd': function(d) {
            var leftEdge = bolus.x(d) + opts.bolusStroke / 2;
            var rightEdge = leftEdge + opts.width - opts.bolusStroke;
            var bolusHeight = opts.yScale(d.value) + opts.bolusStroke / 2;
            return 'M' + leftEdge + ' ' + bottom + 'L' + rightEdge + ' ' + bottom + 'L' + rightEdge + ' ' + bolusHeight + 'L' + leftEdge + ' ' + bolusHeight + 'Z';
          },
          'stroke-width': opts.bolusStroke,
          'class': 'd3-path-bolus d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });

      // square- and dual-wave boluses
      var extendedBoluses = bolusGroups.filter(function(d) {
        if (d.extended) {
          return d;
        }
      });
      extendedBoluses.append('path')
        .attr({
          'd': function(d) {
            var rightEdge = bolus.x(d) + opts.width;
            var doseHeight = computePathHeight(d);
            var doseEnd = opts.xScale(Date.parse(d.normalTime) + d.duration) - opts.triangleSize / 2;
            return 'M' + rightEdge + ' ' + doseHeight + 'L' + doseEnd + ' ' + doseHeight;
          },
          'stroke-width': opts.bolusStroke,
          'class': function(d){
            if (unknownDeliverySplit(d)) {
              return 'd3-path-extended d3-bolus d3-unknown-delivery-split';
            } else {
              return 'd3-path-extended d3-bolus';
            }
          },
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      extendedBoluses.append('path')
        .attr({
          'd': function(d) {
            var doseHeight = computePathHeight(d);
            var doseEnd = opts.xScale(Date.parse(d.normalTime) + d.duration) - opts.triangleSize;
            return bolus.triangle(doseEnd, doseHeight);
          },
          'stroke-width': opts.bolusStroke,
          'class': 'd3-path-extended-triangle d3-bolus',
          'id': function(d) {
            return 'bolus_' + d._id;
          }
        });
      boluses.exit().remove();

      // tooltips
      d3.selectAll('.d3-rect-bolus, .d3-rect-recommended').on('mouseover', function(d) {
        bolus.addTooltip(d, bolus.getTooltipCategory(d));
        opts.emitter.emit('bolusTooltipOn', Date.parse(d.normalTime));
      });
      d3.selectAll('.d3-rect-bolus, .d3-rect-recommended').on('mouseout', function(d) {
        d3.select('#tooltip_' + d._id).remove();
        opts.emitter.emit('bolusTooltipOff', Date.parse(d.normalTime));
      });
    });
  }

  function formatValue(x) {
    var formatted = d3.format('.3f')(x);
    // remove zero-padding on the right
    while (formatted[formatted.length - 1] === '0') {
      formatted = formatted.slice(0, formatted.length - 1);
    }
    if (formatted[formatted.length - 1] === '.') {
      formatted = formatted + '0';
    }
    return formatted;
  }

  bolus.getRecommendedBolusTooltipText = function(datum) {
    return formatValue(datum.recommended) + "U recom'd";
  };

  bolus.getExtendedBolusTooltipText = function(datum) {
    if (unknownDeliverySplit(datum)) {
      return 'Split unknown';
    }
    return format.percentage(datum.extendedDelivery / datum.value) + ' ' + bolus.timespan(datum);
  };

  bolus.getTooltipCategory = function(datum) {
    var category;
    // when there's no 'recommended' field
    if (datum.recommended == null) {
      if (datum.extended == null) {
        category = 'unspecial';
      } else {
        category = 'two-line';
      }
    } else {
      if ((datum.extended == null) && (datum.recommended === datum.value)) {
        category = 'unspecial';
      } else if ((datum.extended == null) && (datum.recommended !== datum.value)) {
        category = 'two-line';
      } else if ((datum.recommended === datum.value) && (datum.extended != null)) {
        category = 'two-line';
      } else if ((datum.recommended !== datum.value) && (datum.extended != null)) {
        category = 'three-line';
      }
    }
    return category;
  };

  bolus.addTooltip = function(datum, category) {
    var tooltipWidth = opts.classes[category].width;
    var tooltipHeight = opts.classes[category].height;

    d3.select('#' + 'tidelineTooltips_bolus')
      .call(pool.tooltips(),
        datum,
        // tooltipXPos
        opts.xScale(Date.parse(datum.normalTime)),
        'bolus',
        // timestamp
        true,
        opts.classes[category].tooltip,
        tooltipWidth,
        tooltipHeight,
        // imageX
        opts.xScale(Date.parse(datum.normalTime)),
        // imageY
        function() {
          return pool.height() - tooltipHeight;
        },
        // textX
        opts.xScale(Date.parse(datum.normalTime)) + tooltipWidth / 2,
        // textY
        function() {
          if (category === 'unspecial') {
            return pool.height() - tooltipHeight * (9/16);
          } else if (category === 'two-line') {
            return pool.height() - tooltipHeight * (3/4);
          } else if (category === 'three-line') {
            return pool.height() - tooltipHeight * (13/16);
          } else {
            return pool.height() - tooltipHeight;
          }

        },
        // customText
        (function() {
          return formatValue(datum.value) + 'U';
        }()),
        // tspan
        (function() {
          if (datum.extended) {
            return ' total';
          }
        }())
      );

    if (category === 'two-line') {
      var twoLineSelection = d3.select('#tooltip_' + datum._id).select('.d3-tooltip-text-group').append('text')
        .attr({
          'class': 'd3-tooltip-text d3-bolus',
          'x': opts.xScale(Date.parse(datum.normalTime)) + tooltipWidth / 2,
          'y': pool.height() - tooltipHeight / 3
        })
        .append('tspan');

      if ((datum.recommended != null) && (datum.recommended !== datum.value)) {
        twoLineSelection.text(bolus.getRecommendedBolusTooltipText(datum));
      }
      else if (datum.extended != null) {
        twoLineSelection.text(bolus.getExtendedBolusTooltipText(datum));
      }

      twoLineSelection.attr('class', 'd3-bolus');
    } else if (category === 'three-line') {
      d3.select('#tooltip_' + datum._id).select('.d3-tooltip-text-group').append('text')
        .attr({
          'class': 'd3-tooltip-text d3-bolus',
          'x': opts.xScale(Date.parse(datum.normalTime)) + tooltipWidth / 2,
          'y': pool.height() - tooltipHeight / 2
        })
        .append('tspan')
        .text(bolus.getRecommendedBolusTooltipText(datum))
        .attr('class', 'd3-bolus');

      d3.select('#tooltip_' + datum._id).select('.d3-tooltip-text-group').append('text')
        .attr({
          'class': 'd3-tooltip-text d3-bolus',
          'x': opts.xScale(Date.parse(datum.normalTime)) + tooltipWidth / 2,
          'y': pool.height() - tooltipHeight / 4
        })
        .append('tspan')
        .text(bolus.getExtendedBolusTooltipText(datum))
        .attr('class', 'd3-bolus');
    }
  };

  bolus.timespan = function(datum) {
    var dur = Duration.parse(datum.duration + 'ms');
    var hours = dur.hours();
    var minutes = dur.minutes() - (hours * 60);
    if (hours !== 0) {
      if (hours === 1) {
        switch(minutes) {
        case 0:
          return 'over ' + hours + ' hr';
        case 15:
          return 'over ' + hours + QUARTER + ' hr';
        case 20:
          return 'over ' + hours + THIRD + ' hr';
        case 30:
          return 'over ' + hours + HALF + ' hr';
        case 40:
          return 'over ' + hours + TWO_THIRDS + ' hr';
        case 45:
          return 'over ' + hours + THREE_QUARTER + ' hr';
        default:
          return 'over ' + hours + ' hr ' + minutes + ' min';
        }
      } else {
        switch(minutes) {
        case 0:
          return 'over ' + hours + ' hrs';
        case 15:
          return 'over ' + hours + QUARTER + ' hrs';
        case 20:
          return 'over ' + hours + THIRD + ' hrs';
        case 30:
          return 'over ' + hours + HALF + ' hrs';
        case 40:
          return 'over ' + hours + TWO_THIRDS + ' hrs';
        case 45:
          return 'over ' + hours + THREE_QUARTER + ' hrs';
        default:
          return 'over ' + hours + ' hrs ' + minutes + ' min';
        }
      }
    } else {
      return 'over ' + minutes + ' min';
    }
  };

  bolus.x = function(datum) {
    return opts.xScale(Date.parse(datum.normalTime)) - opts.width/2;
  };

  bolus.triangle = function(x, y) {
    var top = (x + opts.triangleSize) + ' ' + (y + opts.triangleSize/2);
    var bottom = (x + opts.triangleSize) + ' ' + (y - opts.triangleSize/2);
    var point = x + ' ' + y;
    return 'M' + top + 'L' + bottom + 'L' + point + 'Z';
  };

  return bolus;
};
<|endoftext|>


<commit_before>function puts(string) {console.log(string);};

function add(a,b) {
    return a + b;
}
/* these should eventually do ruby-style case dispatch, and should be replaced
 with calls to naked js primitives when possible. */
Number.prototype.rubyplus = function(a) { return a + this; };
Number.prototype.rubyplus = function(a) { return a - this; };
<commit_msg>added rubytruthy to handle cases in which we want to do short-circuiting eval, eg: a && b .  js has bizarre semantics in that 0, '', and false all == false.<commit_after>function puts(string) {console.log(string);};

function add(a,b) {
    return a + b;
}
/* these should eventually do ruby-style case dispatch, and should be replaced
 with calls to naked js primitives when possible. */
Number.prototype.rubyplus = function(a) { return a + this; };
Number.prototype.rubyminus = function(a) { return a - this; };
Object.prototype.rubytruthy = function () { return (this == null || (typeof(this) == 'boolean' && this == false)) ? false : this; };
<|endoftext|>


<commit_before>/* global Module */

/* MMM-RottenTomatoes.js
 *
 * Magic Mirror
 * Module: MMM-RottenTomatoes
 *
 * Magic Mirror By Michael Teeuw http://michaelteeuw.nl
 * MIT Licensed.
 *
 * Module MMM-RottenTomatoes By Adam Moses http://adammoses.com
 * MIT Licensed.
 */

Module.register("MMM-RottenTomatoes", {
	// setup the default config options
	defaults: {
		// optional
		showHeader: true,
		showOpeningThisWeek: true,
		showBoxOffice: true,
		showComingSoon: true,
		limitOpeningThisWeek: 5,
		limitBoxOffice: 5,
		limitComingSoon: 3,
		boxOfficeAfter: true,
		mergeOpeningAndComingSoon: true,
	},
	// the start function
	start: function() {
		// log starting
		Log.info("Starting module: " + this.name);
		// set refresh rate to 12 hours
		this.config.refreshRate = 12 * 60 * 60 * 1000;
        // set an identier config tag
		this.config.identifier = this.identifier;
		// set loaded, error, and the update to init values
		this.loaded = false;
		this.errorMessage = null;
		// set the header to this place
        if (this.config.showHeader) {
            this.data.header = 'Rotten Tomatoes';
        }
		if (this.config.limitOpeningThisWeek == 0)
			this.config.limitOpeningThisWeek = 100;
		if (this.config.limitBoxOffice == 0)
			this.config.limitBoxOffice = 100;
		if (this.config.limitComingSoon == 0)
			this.config.limitComingSoon = 100;
        // add this config to the helper functions
		this.sendSocketNotification('ROTTEN_TOMATOES_REGISTER_CONFIG', this.config);
	},
	// the socket handler
	socketNotificationReceived: function(notification, payload) {
		// if an update was received
		if (notification === "ROTTEN_TOMATOES_UPDATE") {
			// check this is for this module based on the woeid
			if (payload.identifier === this.identifier)
			{
				// set loaded flag, set the update, and call update dom
				this.rtData = payload.rtData;
                this.loaded = true;
                this.updateDom();
			}
		}
        // if sent error notice
        if (notification === "ROTTEN_TOMATOES_TOO_MANY_ERRORS") {
            this.errorMessage("There was an error.");
            if (this.updateTimer !== null)
                clearTimeout(this.updateTimer);
            this.updateTimer = null;
            this.updateDom();
        }
	},
	//
	cleanScore: function(theScore) {
		if (theScore.indexOf('%') == -1)
			return '--';
		else
			return theScore;
	},
	// the get dom handler
	getDom: function() {
        // if an error, say so
        if (this.errorMessage !== null) {
            var wrapper = document.createElement("div");
			wrapper.className = "small";
			wrapper.innerHTML = this.errorMessage;
			return wrapper;
        }
		// if nothing loaded yet, put in placeholder text
		if (!this.loaded) {
			var wrapper = document.createElement("div");
			wrapper.className = "small";
			wrapper.innerHTML = "Awaiting Update...";
			return wrapper;
		}
		var titleSize = 'small';
		var movieSize = 'xsmall';
		var wrapper = document.createElement("table");
		// do opening this week
		var allOTWandCSRows = [ ];
		var otwData = this.rtData.openingThisWeek;
		var otwTitleTR = document.createElement("tr");
		otwTitleTR.className = titleSize;
		var otwTitleTD = document.createElement("td");
		otwTitleTD.innerHTML = "Opening This Week";
		if (this.config.mergeOpeningAndComingSoon)
			otwTitleTD.innerHTML = "Opening / Coming Soon";
		otwTitleTD.colSpan = "3";
		otwTitleTR.appendChild(otwTitleTD);
		allOTWandCSRows.push(otwTitleTR);
		for (var cIndex = 0;
				(cIndex < otwData.length) && (cIndex < this.config.limitOpeningThisWeek);
				cIndex++) {
			var cOTW = otwData[cIndex];
			var otwRowTR = document.createElement("tr");
			otwRowTR.className = movieSize;
			var otwRowMeter = document.createElement("td");
			otwRowMeter.innerHTML = this.cleanScore(cOTW.meter) + "  ";
			otwRowTR.appendChild(otwRowMeter);
			var otwRowTitle = document.createElement("td");
			otwRowTitle.innerHTML = cOTW.title + "  ";
			otwRowTitle.align = 'left';
			otwRowTR.appendChild(otwRowTitle);
			var otwRowDate = document.createElement("td");
			otwRowDate.innerHTML = "  " + cOTW.date;
			otwRowTR.appendChild(otwRowDate);
			allOTWandCSRows.push(otwRowTR);
		}
		// do opening this week
		var csData = this.rtData.comingSoon;
		var csTitleTR = document.createElement("tr");
		csTitleTR.className = titleSize;
		var csTitleTD = document.createElement("td");
		csTitleTD.innerHTML = "Coming Soon";
		csTitleTD.colSpan = "3";
		csTitleTR.appendChild(csTitleTD);
		if (!this.config.mergeOpeningAndComingSoon)
			allOTWandCSRows.push(csTitleTR);
		for (var cIndex = 0;
				(cIndex < csData.length) && (cIndex < this.config.limitComingSoon);
				cIndex++) {
			var ccs = csData[cIndex];
			var csRowTR = document.createElement("tr");
			csRowTR.className = movieSize;
			var csRowMeter = document.createElement("td");
			csRowMeter.innerHTML = this.cleanScore(ccs.meter) + "  ";
			csRowTR.appendChild(csRowMeter);
			var csRowTitle = document.createElement("td");
			csRowTitle.innerHTML = ccs.title + "  ";
			csRowTitle.align = 'left';
			csRowTR.appendChild(csRowTitle);
			var csRowDate = document.createElement("td");
			csRowDate.innerHTML = "  " + ccs.date;
			csRowTR.appendChild(csRowDate);
			allOTWandCSRows.push(csRowTR);
		}
		// do box office
		var boRows = [ ];
		var boData = this.rtData.boxOffice;
		var boTitleTR = document.createElement("tr");
		boTitleTR.className = titleSize;
		var boTitleTD = document.createElement("td");
		boTitleTD.innerHTML = "Box Office";
		boTitleTD.width = "250px";
		boTitleTD.colSpan = "3";
		boTitleTR.appendChild(boTitleTD);
		boRows.push(boTitleTR);
		for (var cIndex = 0;
				(cIndex < boData.length) && (cIndex < this.config.limitBoxOffice);
				cIndex++) {
			var cbo = boData[cIndex];
			var boRowTR = document.createElement("tr");
			boRowTR.className = movieSize;
			var boRowMeter = document.createElement("td");
			boRowMeter.innerHTML = this.cleanScore(cbo.meter) + "  ";
			boRowTR.appendChild(boRowMeter);
			var boRowTitle = document.createElement("td");
			boRowTitle.innerHTML = cbo.title + "  ";
			boRowTitle.align = 'left';
			boRowTR.appendChild(boRowTitle);
			var boRowGross = document.createElement("td");
			boRowGross.innerHTML = "  " + cbo.gross;
			boRowTR.appendChild(boRowGross);
			boRows.push(boRowTR);
		}
		// build the table
        var allRows = [ ];
        // if set to show box office first, do that
		if (!this.config.boxOfficeAfter)
			allRows = allRows.concat(boRows);
        // add opening this week and coming soon rows
		allRows = allRows.concat(allOTWandCSRows);
        // if set to show box office after, do that
		if (this.config.boxOfficeAfter)
			allRows = allRows.concat(boRows);
        // add all rows to the return table wrapper
		for (var cIndex = 0; cIndex < allRows.length; cIndex++) {
			wrapper.appendChild(allRows[cIndex]);
		}
        // return table wrapper
		return wrapper;
	}
});

// ------------ end -------------
<commit_msg>fixed missing config usage<commit_after>/* global Module */

/* MMM-RottenTomatoes.js
 *
 * Magic Mirror
 * Module: MMM-RottenTomatoes
 *
 * Magic Mirror By Michael Teeuw http://michaelteeuw.nl
 * MIT Licensed.
 *
 * Module MMM-RottenTomatoes By Adam Moses http://adammoses.com
 * MIT Licensed.
 */

Module.register("MMM-RottenTomatoes", {
	// setup the default config options
	defaults: {
		// optional
		showHeader: true,
		showOpeningThisWeek: true,
		showBoxOffice: true,
		showComingSoon: true,
		limitOpeningThisWeek: 5,
		limitBoxOffice: 5,
		limitComingSoon: 3,
		boxOfficeAfter: true,
		mergeOpeningAndComingSoon: true,
	},
	// the start function
	start: function() {
		// log starting
		Log.info("Starting module: " + this.name);
		// set refresh rate to 12 hours
		this.config.refreshRate = 12 * 60 * 60 * 1000;
        // set an identier config tag
		this.config.identifier = this.identifier;
		// set loaded, error, and the update to init values
		this.loaded = false;
		this.errorMessage = null;
		// set the header to this place
        if (this.config.showHeader) {
            this.data.header = 'Rotten Tomatoes';
        }
		if (this.config.limitOpeningThisWeek == 0)
			this.config.limitOpeningThisWeek = 100;
		if (this.config.limitBoxOffice == 0)
			this.config.limitBoxOffice = 100;
		if (this.config.limitComingSoon == 0)
			this.config.limitComingSoon = 100;
        // add this config to the helper functions
		this.sendSocketNotification('ROTTEN_TOMATOES_REGISTER_CONFIG', this.config);
	},
	// the socket handler
	socketNotificationReceived: function(notification, payload) {
		// if an update was received
		if (notification === "ROTTEN_TOMATOES_UPDATE") {
			// check this is for this module based on the woeid
			if (payload.identifier === this.identifier)
			{
				// set loaded flag, set the update, and call update dom
				this.rtData = payload.rtData;
                this.loaded = true;
                this.updateDom();
			}
		}
        // if sent error notice
        if (notification === "ROTTEN_TOMATOES_TOO_MANY_ERRORS") {
            this.errorMessage("There was an error.");
            if (this.updateTimer !== null)
                clearTimeout(this.updateTimer);
            this.updateTimer = null;
            this.updateDom();
        }
	},
	//
	cleanScore: function(theScore) {
		if (theScore.indexOf('%') == -1)
			return '--';
		else
			return theScore;
	},
	// the get dom handler
	getDom: function() {
        // if an error, say so
        if (this.errorMessage !== null) {
            var wrapper = document.createElement("div");
			wrapper.className = "small";
			wrapper.innerHTML = this.errorMessage;
			return wrapper;
        }
		// if nothing loaded yet, put in placeholder text
		if (!this.loaded) {
			var wrapper = document.createElement("div");
			wrapper.className = "small";
			wrapper.innerHTML = "Awaiting Update...";
			return wrapper;
		}
		var titleSize = 'small';
		var movieSize = 'xsmall';
		var wrapper = document.createElement("table");
		// do opening this week
		var allOTWandCSRows = [ ];
		if (this.config.showOpeningThisWeek) {
			var otwData = this.rtData.openingThisWeek;
			var otwTitleTR = document.createElement("tr");
			otwTitleTR.className = titleSize;
			var otwTitleTD = document.createElement("td");
			otwTitleTD.innerHTML = "Opening This Week";
			if (this.config.mergeOpeningAndComingSoon)
				otwTitleTD.innerHTML = "Opening / Coming Soon";
			otwTitleTD.colSpan = "3";
			otwTitleTR.appendChild(otwTitleTD);
			allOTWandCSRows.push(otwTitleTR);
			for (var cIndex = 0;
					(cIndex < otwData.length) && (cIndex < this.config.limitOpeningThisWeek);
					cIndex++) {
				var cOTW = otwData[cIndex];
				var otwRowTR = document.createElement("tr");
				otwRowTR.className = movieSize;
				var otwRowMeter = document.createElement("td");
				otwRowMeter.innerHTML = this.cleanScore(cOTW.meter) + "  ";
				otwRowTR.appendChild(otwRowMeter);
				var otwRowTitle = document.createElement("td");
				otwRowTitle.innerHTML = cOTW.title + "  ";
				otwRowTitle.align = 'left';
				otwRowTR.appendChild(otwRowTitle);
				var otwRowDate = document.createElement("td");
				otwRowDate.innerHTML = "  " + cOTW.date;
				otwRowTR.appendChild(otwRowDate);
				allOTWandCSRows.push(otwRowTR);
			}
		}
		// do opening this week
		if (this.config.showOpeningThisWeek) {
			var csData = this.rtData.comingSoon;
			var csTitleTR = document.createElement("tr");
			csTitleTR.className = titleSize;
			var csTitleTD = document.createElement("td");
			csTitleTD.innerHTML = "Coming Soon";
			csTitleTD.colSpan = "3";
			csTitleTR.appendChild(csTitleTD);
			if (!this.config.mergeOpeningAndComingSoon)
				allOTWandCSRows.push(csTitleTR);
			for (var cIndex = 0;
					(cIndex < csData.length) && (cIndex < this.config.limitComingSoon);
					cIndex++) {
				var ccs = csData[cIndex];
				var csRowTR = document.createElement("tr");
				csRowTR.className = movieSize;
				var csRowMeter = document.createElement("td");
				csRowMeter.innerHTML = this.cleanScore(ccs.meter) + "  ";
				csRowTR.appendChild(csRowMeter);
				var csRowTitle = document.createElement("td");
				csRowTitle.innerHTML = ccs.title + "  ";
				csRowTitle.align = 'left';
				csRowTR.appendChild(csRowTitle);
				var csRowDate = document.createElement("td");
				csRowDate.innerHTML = "  " + ccs.date;
				csRowTR.appendChild(csRowDate);
				allOTWandCSRows.push(csRowTR);
			}
		}
		// do box office
		var boRows = [ ];
		if (this.config.showBoxOffice) {
			var boData = this.rtData.boxOffice;
			var boTitleTR = document.createElement("tr");
			boTitleTR.className = titleSize;
			var boTitleTD = document.createElement("td");
			boTitleTD.innerHTML = "Box Office";
			boTitleTD.width = "250px";
			boTitleTD.colSpan = "3";
			boTitleTR.appendChild(boTitleTD);
			boRows.push(boTitleTR);
			for (var cIndex = 0;
					(cIndex < boData.length) && (cIndex < this.config.limitBoxOffice);
					cIndex++) {
				var cbo = boData[cIndex];
				var boRowTR = document.createElement("tr");
				boRowTR.className = movieSize;
				var boRowMeter = document.createElement("td");
				boRowMeter.innerHTML = this.cleanScore(cbo.meter) + "  ";
				boRowTR.appendChild(boRowMeter);
				var boRowTitle = document.createElement("td");
				boRowTitle.innerHTML = cbo.title + "  ";
				boRowTitle.align = 'left';
				boRowTR.appendChild(boRowTitle);
				var boRowGross = document.createElement("td");
				boRowGross.innerHTML = "  " + cbo.gross;
				boRowTR.appendChild(boRowGross);
				boRows.push(boRowTR);
			}
		}
		// build the table
        var allRows = [ ];
        // if set to show box office first, do that
		if (!this.config.boxOfficeAfter)
			allRows = allRows.concat(boRows);
        // add opening this week and coming soon rows
		allRows = allRows.concat(allOTWandCSRows);
        // if set to show box office after, do that
		if (this.config.boxOfficeAfter)
			allRows = allRows.concat(boRows);
        // add all rows to the return table wrapper
		for (var cIndex = 0; cIndex < allRows.length; cIndex++) {
			wrapper.appendChild(allRows[cIndex]);
		}
        // return table wrapper
		return wrapper;
	}
});

// ------------ end -------------
<|endoftext|>


<commit_before>// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

import browser from "webextension-polyfill";
import parser from "ua-parser-js";
import { verifyFile, FileTypes, migrateTestCase, migrateProject } from "./legacy/migrate";
import UiState from "../stores/view/UiState";
import ModalState from "../stores/view/ModalState";
import Selianize, { ParseError } from "selianize";
import chromeGetFile from "./filesystem/chrome";
import firefoxGetFile from "./filesystem/firefox";

export const supportedFileFormats = ".side, text/html";
const parsedUA = parser(window.navigator.userAgent);

export function getFile(path) {
  const browserName = parsedUA.browser.name;
  return (() => {
    if (browserName === "Chrome") {
      return chromeGetFile(path);
    } else if (browserName === "Firefox") {
      return firefoxGetFile(path);
    } else {
      return Promise.reject(new Error("Operation is not supported in this browser"));
    }
  })().then(blob => {
    return new Promise((res) => {
      const reader = new FileReader();
      reader.addEventListener("load", () => {
        res(reader.result);
      });
      reader.readAsDataURL(blob);
    });
  });
}

export function loadAsText(blob) {
  return new Promise((res) => {
    const fileReader = new FileReader();
    fileReader.onload = (e) => {
      res(e.target.result);
    };

    fileReader.readAsText(blob);
  });
}

export function saveProject(_project) {
  const project = _project.toJS();
  project.version = "1.0";
  downloadProject(project);
  UiState.saved();
}

function downloadProject(project) {
  return exportProject(project).then(code => {
    project.code = code;
    return browser.downloads.download({
      filename: project.name + ".side",
      url: createBlob("application/json", JSON.stringify(project)),
      saveAs: true,
      conflictAction: "overwrite"
    });
  });
}

function exportProject(project) {
  return Selianize(project).catch(err => {
    const markdown = ParseError(err && err.message || err);
    ModalState.showAlert({
      title: "Error exporting project",
      description: markdown,
      confirmLabel: "Download log",
      cancelLabel: "Close"
    }, (choseDownload) => {
      if (choseDownload) {
        browser.downloads.download({
          filename: project.name + "-logs.md",
          url: createBlob("text/markdown", markdown),
          saveAs: true,
          conflictAction: "overwrite"
        });
      }
    });
  });
}

let previousFile = null;
function createBlob(mimeType, data) {
  const blob = new Blob([data], {
    type: "text/plain"
  });
  // If we are replacing a previously generated file we need to
  // manually revoke the object URL to avoid memory leaks.
  if (previousFile !== null) {
    window.URL.revokeObjectURL(previousFile);
  }
  previousFile = window.URL.createObjectURL(blob);
  return previousFile;
}

export function loadProject(project, file) {
  function displayError(error) {
    ModalState.showAlert({
      title: "Error migrating project",
      description: error.message,
      confirmLabel: "Close"
    });
  }
  loadAsText(file).then((contents) => {
    if (/\.side$/.test(file.name)) {
      loadJSONProject(project, contents);
    } else  {
      try {
        const type = verifyFile(contents);
        if (type === FileTypes.Suite) {
          ModalState.importSuite(contents, (files) => {
            project.fromJS(migrateProject(files));
          });
        } else if (type === FileTypes.TestCase) {
          project.fromJS(migrateTestCase(contents));
        }
      } catch (error) {
        displayError(error);
      }
    }
  });
}

function loadJSONProject(project, data) {
  project.fromJS(JSON.parse(data));
}
<commit_msg>dont download the project if there are errors selianizing it<commit_after>// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

import browser from "webextension-polyfill";
import parser from "ua-parser-js";
import { verifyFile, FileTypes, migrateTestCase, migrateProject } from "./legacy/migrate";
import UiState from "../stores/view/UiState";
import ModalState from "../stores/view/ModalState";
import Selianize, { ParseError } from "selianize";
import chromeGetFile from "./filesystem/chrome";
import firefoxGetFile from "./filesystem/firefox";

export const supportedFileFormats = ".side, text/html";
const parsedUA = parser(window.navigator.userAgent);

export function getFile(path) {
  const browserName = parsedUA.browser.name;
  return (() => {
    if (browserName === "Chrome") {
      return chromeGetFile(path);
    } else if (browserName === "Firefox") {
      return firefoxGetFile(path);
    } else {
      return Promise.reject(new Error("Operation is not supported in this browser"));
    }
  })().then(blob => {
    return new Promise((res) => {
      const reader = new FileReader();
      reader.addEventListener("load", () => {
        res(reader.result);
      });
      reader.readAsDataURL(blob);
    });
  });
}

export function loadAsText(blob) {
  return new Promise((res) => {
    const fileReader = new FileReader();
    fileReader.onload = (e) => {
      res(e.target.result);
    };

    fileReader.readAsText(blob);
  });
}

export function saveProject(_project) {
  const project = _project.toJS();
  project.version = "1.0";
  downloadProject(project);
  UiState.saved();
}

function downloadProject(project) {
  return exportProject(project).then(code => {
    project.code = code;
    return browser.downloads.download({
      filename: project.name + ".side",
      url: createBlob("application/json", JSON.stringify(project)),
      saveAs: true,
      conflictAction: "overwrite"
    });
  });
}

function exportProject(project) {
  return Selianize(project).catch(err => {
    const markdown = ParseError(err && err.message || err);
    ModalState.showAlert({
      title: "Error saving project",
      description: markdown,
      confirmLabel: "Download log",
      cancelLabel: "Close"
    }, (choseDownload) => {
      if (choseDownload) {
        browser.downloads.download({
          filename: project.name + "-logs.md",
          url: createBlob("text/markdown", markdown),
          saveAs: true,
          conflictAction: "overwrite"
        });
      }
    });
    return Promise.reject();
  });
}

let previousFile = null;
function createBlob(mimeType, data) {
  const blob = new Blob([data], {
    type: "text/plain"
  });
  // If we are replacing a previously generated file we need to
  // manually revoke the object URL to avoid memory leaks.
  if (previousFile !== null) {
    window.URL.revokeObjectURL(previousFile);
  }
  previousFile = window.URL.createObjectURL(blob);
  return previousFile;
}

export function loadProject(project, file) {
  function displayError(error) {
    ModalState.showAlert({
      title: "Error migrating project",
      description: error.message,
      confirmLabel: "Close"
    });
  }
  loadAsText(file).then((contents) => {
    if (/\.side$/.test(file.name)) {
      loadJSONProject(project, contents);
    } else  {
      try {
        const type = verifyFile(contents);
        if (type === FileTypes.Suite) {
          ModalState.importSuite(contents, (files) => {
            project.fromJS(migrateProject(files));
          });
        } else if (type === FileTypes.TestCase) {
          project.fromJS(migrateTestCase(contents));
        }
      } catch (error) {
        displayError(error);
      }
    }
  });
}

function loadJSONProject(project, data) {
  project.fromJS(JSON.parse(data));
}
<|endoftext|>


<commit_before>import React from "react";

import { shallow, mount } from "enzyme";

import { Vega, VegaLite, VegaEmbed } from "../src/";

const cars = require("vega-lite/data/cars.json");

const spec = {
  description: "A scatterplot showing horsepower and miles per gallons.",
  data: {
    values: cars
  },
  mark: "point",
  encoding: {
    x: { field: "Horsepower", type: "quantitative" },
    y: { field: "Miles_per_Gallon", type: "quantitative" },
    color: { field: "Origin", type: "nominal" },
    shape: { field: "Origin", type: "nominal" }
  }
};

describe("Vega", () => {
  it("renders VegaEmbed with embedMode vega", () => {
    const wrapper = shallow(<Vega data={spec} />);

    expect(wrapper.name()).toEqual("VegaEmbed");
    expect(wrapper.props().embedMode).toEqual("vega");
  });
});

describe("VegaLite", () => {
  it("renders VegaEmbed with embedMode vega-lite", () => {
    const wrapper = shallow(<VegaLite data={spec} />);

    expect(wrapper.name()).toEqual("VegaEmbed");
    expect(wrapper.props().embedMode).toEqual("vega-lite");
  });
});

describe("VegaEmbed", () => {
  it("embeds vega", () => {
    const spy = jest.fn();
    const wrapper = mount(
      <VegaEmbed data={spec} embedMode="vega-lite" renderedCallback={spy} />
    );

    const element = wrapper.instance();

    expect(element.shouldComponentUpdate({ data: "324" })).toEqual(true);
  });

  it("embeds vega and handles updates", () => {
    const spy = jest.fn();
    const wrapper = mount(
      <VegaEmbed data={spec} embedMode="vega-lite" renderedCallback={spy} />
    );
    wrapper.render();

    const spy2 = jest.fn();

    wrapper.setProps({
      data: {
        data: {
          values: cars
        },
        mark: "circle",
        encoding: {
          x: { field: "Horsepower", type: "quantitative" },
          y: { field: "Miles_per_Gallon", type: "quantitative" }
        }
      },
      renderedCallback: spy2
    });
  });
});
<commit_msg>Add tests for vega 3<commit_after>import React from "react";

import { shallow, mount } from "enzyme";

import { Vega2, Vega3, VegaLite1, VegaLite2, VegaEmbed } from "../src/";

const cars = require("vega-lite/data/cars.json");

const spec = {
  description: "A scatterplot showing horsepower and miles per gallons.",
  data: {
    values: cars
  },
  mark: "point",
  encoding: {
    x: { field: "Horsepower", type: "quantitative" },
    y: { field: "Miles_per_Gallon", type: "quantitative" },
    color: { field: "Origin", type: "nominal" },
    shape: { field: "Origin", type: "nominal" }
  }
};

describe("Vega2", () => {
  it("renders VegaEmbed with embedMode vega", () => {
    const wrapper = shallow(<Vega2 data={spec} />);

    expect(wrapper.name()).toEqual("VegaEmbed");
    expect(wrapper.props().embedMode).toEqual("vega");
    expect(wrapper.props().version).toEqual("vega2");
  });
});

describe("Vega3", () => {
  it("renders VegaEmbed with embedMode vega", () => {
    const wrapper = shallow(<Vega3 data={spec} />);

    expect(wrapper.name()).toEqual("VegaEmbed");
    expect(wrapper.props().embedMode).toEqual("vega");
    expect(wrapper.props().version).toEqual("vega3");
  });
});

describe("VegaLite1", () => {
  it("renders VegaEmbed with embedMode vega-lite", () => {
    const wrapper = shallow(<VegaLite1 data={spec} />);

    expect(wrapper.name()).toEqual("VegaEmbed");
    expect(wrapper.props().embedMode).toEqual("vega-lite");
    expect(wrapper.props().version).toEqual("vega2");
  });
});

describe("VegaLite2", () => {
  it("renders VegaEmbed with embedMode vega-lite", () => {
    const wrapper = shallow(<VegaLite2 data={spec} />);

    expect(wrapper.name()).toEqual("VegaEmbed");
    expect(wrapper.props().embedMode).toEqual("vega-lite");
    expect(wrapper.props().version).toEqual("vega3");
  });
});

describe("VegaEmbed", () => {
  it("embeds vega", () => {
    const spy = jest.fn();
    const wrapper = mount(
      <VegaEmbed data={spec} embedMode="vega-lite" renderedCallback={spy} />
    );

    const element = wrapper.instance();

    expect(element.shouldComponentUpdate({ data: "324" })).toEqual(true);
  });

  it("embeds vega and handles updates", () => {
    const spy = jest.fn();
    const wrapper = mount(
      <VegaEmbed data={spec} embedMode="vega-lite" renderedCallback={spy} />
    );
    wrapper.render();

    const spy2 = jest.fn();

    wrapper.setProps({
      data: {
        data: {
          values: cars
        },
        mark: "circle",
        encoding: {
          x: { field: "Horsepower", type: "quantitative" },
          y: { field: "Miles_per_Gallon", type: "quantitative" }
        }
      },
      renderedCallback: spy2
    });
  });
});
<|endoftext|>


<commit_before>Graph = (function() {
    var statusColors = {
        "FAILED":"#DD0000",
        "RUNNING":"#0044DD",
        "PENDING":"#EEBB00",
        "DONE":"#00DD00",
        "DISABLED":"#808080"
    };

    /* Line height for items in task status legend */
    var legendLineHeight = 20;

    /* Height of vertical space between nodes */
    var nodeHeight = 10;

    /* Amount of horizontal space given for each node */
    var nodeWidth = 200;

    /* Calculate minimum SVG height required for legend */
    var legendMaxY = (function () {
        return Object.keys(statusColors).length * legendLineHeight + ( legendLineHeight / 2 )
    })();

    function nodeFromTask(task) {
        var deps = task.deps;
        deps.sort();
        return {
            name: task.name,
            taskId: task.taskId,
            status: task.status,
            trackingUrl: "#"+task.taskId,
            deps: deps,
            params: task.params,
            priority: task.priority,
            depth: -1
        };
    }

    /* Convert array to dict by indexing on propertyName */
    function uniqueIndexByProperty(data, propertyName) {
        var nodeIndex = {};
        $.each(data, function(i, dataPoint) {
            nodeIndex[dataPoint[propertyName]] = i;
        });
        return nodeIndex;
    }

    /* Create edges between the supplied node using the deps property of each node */
    function createDependencyEdges(nodes, nodeIndex) {
        var edges = [];
        $.each(nodes, function(i, task) {
            $.each(task.deps, function(j, dep) {
                if (nodeIndex[dep]) {
                    edges.push({
                        source: nodes[nodeIndex[task.taskId]],
                        target: nodes[nodeIndex[dep]]
                    });
                }
            });
        });
        return edges;
    }

    /* Compute the maximum depth of each node for layout purposes, returns the number
       of nodes at each depth level (for layout purposes) */
    function computeDepth(nodes, nodeIndex) {

        // memoized to take linear time in number of nodes and edges
        function height(taskId) {
            var node = nodes[nodeIndex[taskId]];

            // storing height in depth here to save space
            if (node.depth === undefined || node.depth === -1) {
                node.depth = Math.max(0, Math.max.apply(null, node.deps.map(height)) + 1);
            }
            return node.depth;
        }
        var max_height = height(nodes[0].taskId);

        // convert height to depth
        $.each(nodes, function(i, node) {
            if (node.depth !== undefined && node.depth !== -1) {
                node.depth = max_height - node.depth;
            }
        })

        // linear graph traversal, visiting each node once
        var rowSizes = [];
        function placeNodes(n, depth) {
            if (rowSizes[depth] === undefined) {
                rowSizes[depth] = 0;
            }
            if (n.xOrder === undefined && depth === n.depth) {
                n.xOrder = rowSizes[depth];
                rowSizes[depth]++;
                $.each(n.deps, function(i, dep) {
                    if (nodeIndex[dep]) {
                        placeNodes(nodes[nodeIndex[dep]], depth + 1);
                    }
                });
            }
        }
        placeNodes(nodes[0], 0);

        return rowSizes;
    }

    /* Format nodes according to their depth and horizontal sort order.
       Algorithm: evenly distribute nodes along each depth level, offsetting each
       by the text line height to prevent overlapping text. This is done within
       multiple columns to keep the levels from being too tall. The column width
       is at least nodeWidth to ensure readability. The height of each level is
       determined by number of nodes divided by number of columns, rounded up. */
    function layoutNodes(nodes, rowSizes) {
        var numCols = Math.max(2, Math.floor(graphWidth / nodeWidth))
        function rowStartPosition(depth) {
            if (depth === 0) return 20;
            var rowHeight = Math.ceil(rowSizes[depth-1] / numCols);
            return rowStartPosition(depth-1)+Math.max(rowHeight * nodeHeight + 100);
        }
        $.each(nodes, function(i, node) {
            var levelCols = Math.min(rowSizes[node.depth], numCols)
            var numRows = Math.ceil(rowSizes[node.depth] / levelCols);
            var row = node.xOrder % numRows;
            var col = node.xOrder / numRows;
            node.x = ((col + 1) / (levelCols + 1)) * (graphWidth - 200);
            node.y = rowStartPosition(node.depth) + row * nodeHeight;
        });
    }

    /* Parses a list of tasks to a graph format */
    function createGraph(tasks) {
        if (tasks.length === 0) return {nodes: [], links: []};

        var nodes = $.map(tasks, nodeFromTask);
        var nodeIndex = uniqueIndexByProperty(nodes, "taskId");

        var rowSizes = computeDepth(nodes, nodeIndex);

        nodes = $.map(nodes, function(node) { return node.depth >= 0 ? node: null; });

        layoutNodes(nodes, rowSizes);

        // We need to re-index nodes after filtering
        nodeIndex = uniqueIndexByProperty(nodes, "taskId");
        var edges = createDependencyEdges(nodes, nodeIndex);

        return {
            nodes: nodes,
            links: edges
        };
    }

    function findBounds(nodes) {
        var maxX = 0;
        var maxY = legendMaxY;
        $.each(nodes, function(i, node) {
            if (node.x>maxX) maxX = node.x;
            if (node.y>maxY) maxY = node.y;
        });
        return {
            x:maxX,
            y:maxY
        };
    }

    var graphWidth = window.innerWidth - 80;

    function DependencyGraph(containerElement) {
        this.svg = $(svgElement("svg")).appendTo($(containerElement));
    }

    /* We need custom element creators for svg nodes and xlink attributes because jQuery doesn't support
       namespaces properly */
    function svgElement(name) {
        return document.createElementNS("http://www.w3.org/2000/svg", name);
    }

    function svgLink(url) {
        var element = svgElement("a");
        element.setAttributeNS("http://www.w3.org/1999/xlink", "href", url);
        return element;
    }

    DependencyGraph.prototype.renderGraph = function() {
        var self = this;

        $.each(this.graph.links, function(i, link) {
            var line = $(svgElement("line"))
                        .attr("class","link")
                        .attr("x1", link.source.x)
                        .attr("y1", link.source.y)
                        .attr("x2", link.target.x)
                        .attr("y2", link.target.y)
                        .appendTo(self.svg);
        });

        $.each(this.graph.nodes, function(i, node) {
            var g = $(svgElement("g"))
                .addClass("node")
                .attr("transform", "translate(" + node.x + "," + node.y +")")
                .appendTo(self.svg);

            $(svgElement("circle"))
                .addClass("nodeCircle")
                .attr("r", 7)
                .attr("fill", statusColors[node.status])
                .appendTo(g);
            $(svgLink(node.trackingUrl))
                .append(
                    $(svgElement("text"))
                    .text(node.name)
                    .attr("y", 3))
                .attr("class","graph-node-a")
                .attr("data-task-status", node.status)
                .attr("data-task-id", node.taskId)
                .appendTo(g);

            var titleText = node.name;
            var content = $.map(node.params, function (value, name) { return name + ": " + value; }).join("
");
            g.attr("title", titleText)
                .popover({
                    trigger: 'hover',
                    container: 'body',
                    html: true,
                    placement: 'top',
                    content: content
                });
        });

        // Legend for Task status
        var legend = $(svgElement("g"))
                .addClass("legend")
                .appendTo(self.svg)

        $(svgElement("rect"))
            .attr("x", -1)
            .attr("y", -1)
            .attr("width", "100px")
            .attr("height", legendMaxY + "px")
            .attr("fill", "#FFF")
            .attr("stroke", "#DDD")
            .appendTo(legend);

        var x = 0;
        $.each(statusColors, function(key, color) {
            var c = $(svgElement("circle"))
                .addClass("nodeCircle")
                .attr("r", 7)
                .attr("cx", legendLineHeight)
                .attr("cy", (legendLineHeight-4)+(x*legendLineHeight))
                .attr("fill", color)
                .appendTo(legend)

            $(svgElement("text"))
                .text(key.charAt(0).toUpperCase() + key.substring(1).toLowerCase())
                .attr("x", legendLineHeight + 14)
                .attr("y", legendLineHeight+(x*legendLineHeight))
                .appendTo(legend);

            x++;
        });
    };

    DependencyGraph.prototype.updateData = function(taskList) {
        $('.popover').popover('destroy');
        this.graph = createGraph(taskList);
        bounds = findBounds(this.graph.nodes);
        this.renderGraph();
        this.svg.attr("height", bounds.y+10);
        this.svg.attr("width", graphWidth+10);
        this.svg[0].setAttributeNS("http://www.w3.org/2000/svg", "preserveAspectRatio", "xMidYMid meet");
        this.svg[0].setAttributeNS("http://www.w3.org/2000/svg", "viewBox", "0 0 " + graphWidth + " " + (bounds.y+10));
    };

    return {
        DependencyGraph: DependencyGraph,
        testableMethods: {
            nodeFromTask: nodeFromTask,
            uniqueIndexByProperty: uniqueIndexByProperty,
            createDependencyEdges: createDependencyEdges,
            computeDepth: computeDepth,
            createGraph: createGraph,
            findBounds: findBounds
        }
    };
})();
<commit_msg>Reverts #1296 "Makes computeDepth take linear time"<commit_after>Graph = (function() {
    var statusColors = {
        "FAILED":"#DD0000",
        "RUNNING":"#0044DD",
        "PENDING":"#EEBB00",
        "DONE":"#00DD00",
        "DISABLED":"#808080"
    };

    /* Line height for items in task status legend */
    var legendLineHeight = 20;

    /* Height of vertical space between nodes */
    var nodeHeight = 10;

    /* Amount of horizontal space given for each node */
    var nodeWidth = 200;

    /* Calculate minimum SVG height required for legend */
    var legendMaxY = (function () {
        return Object.keys(statusColors).length * legendLineHeight + ( legendLineHeight / 2 )
    })();

    function nodeFromTask(task) {
        var deps = task.deps;
        deps.sort();
        return {
            name: task.name,
            taskId: task.taskId,
            status: task.status,
            trackingUrl: "#"+task.taskId,
            deps: deps,
            params: task.params,
            priority: task.priority,
            depth: -1
        };
    }

    /* Convert array to dict by indexing on propertyName */
    function uniqueIndexByProperty(data, propertyName) {
        var nodeIndex = {};
        $.each(data, function(i, dataPoint) {
            nodeIndex[dataPoint[propertyName]] = i;
        });
        return nodeIndex;
    }

    /* Create edges between the supplied node using the deps property of each node */
    function createDependencyEdges(nodes, nodeIndex) {
        var edges = [];
        $.each(nodes, function(i, task) {
            $.each(task.deps, function(j, dep) {
                if (nodeIndex[dep]) {
                    edges.push({
                        source: nodes[nodeIndex[task.taskId]],
                        target: nodes[nodeIndex[dep]]
                    });
                }
            });
        });
        return edges;
    }

    /* Compute the maximum depth of each node for layout purposes, returns the number
       of nodes at each depth level (for layout purposes) */
    function computeDepth(nodes, nodeIndex) {
        function descend(n, depth) {
            if (n.depth === undefined || depth > n.depth) {
                n.depth = depth;
                $.each(n.deps, function(i, dep) {
                    if (nodeIndex[dep]) {
                        descend(nodes[nodeIndex[dep]], depth + 1);
                    }
                });
            }
        }
        descend(nodes[0], 0);

        var rowSizes = [];
        function placeNodes(n, depth) {
            if (rowSizes[depth] === undefined) {
                rowSizes[depth] = 0;
            }
            if (n.xOrder === undefined && depth === n.depth) {
                n.xOrder = rowSizes[depth];
                rowSizes[depth]++;
                $.each(n.deps, function(i, dep) {
                    if (nodeIndex[dep]) {
                        placeNodes(nodes[nodeIndex[dep]], depth + 1);
                    }
                });
            }
        }
        placeNodes(nodes[0], 0);

        return rowSizes;
    }

    /* Format nodes according to their depth and horizontal sort order.
       Algorithm: evenly distribute nodes along each depth level, offsetting each
       by the text line height to prevent overlapping text. This is done within
       multiple columns to keep the levels from being too tall. The column width
       is at least nodeWidth to ensure readability. The height of each level is
       determined by number of nodes divided by number of columns, rounded up. */
    function layoutNodes(nodes, rowSizes) {
        var numCols = Math.max(2, Math.floor(graphWidth / nodeWidth))
        function rowStartPosition(depth) {
            if (depth === 0) return 20;
            var rowHeight = Math.ceil(rowSizes[depth-1] / numCols);
            return rowStartPosition(depth-1)+Math.max(rowHeight * nodeHeight + 100);
        }
        $.each(nodes, function(i, node) {
            var levelCols = Math.min(rowSizes[node.depth], numCols)
            var numRows = Math.ceil(rowSizes[node.depth] / levelCols);
            var row = node.xOrder % numRows;
            var col = node.xOrder / numRows;
            node.x = ((col + 1) / (levelCols + 1)) * (graphWidth - 200);
            node.y = rowStartPosition(node.depth) + row * nodeHeight;
        });
    }

    /* Parses a list of tasks to a graph format */
    function createGraph(tasks) {
        if (tasks.length === 0) return {nodes: [], links: []};

        var nodes = $.map(tasks, nodeFromTask);
        var nodeIndex = uniqueIndexByProperty(nodes, "taskId");

        var rowSizes = computeDepth(nodes, nodeIndex);

        nodes = $.map(nodes, function(node) { return node.depth >= 0 ? node: null; });

        layoutNodes(nodes, rowSizes);

        // We need to re-index nodes after filtering
        nodeIndex = uniqueIndexByProperty(nodes, "taskId");
        var edges = createDependencyEdges(nodes, nodeIndex);

        return {
            nodes: nodes,
            links: edges
        };
    }

    function findBounds(nodes) {
        var maxX = 0;
        var maxY = legendMaxY;
        $.each(nodes, function(i, node) {
            if (node.x>maxX) maxX = node.x;
            if (node.y>maxY) maxY = node.y;
        });
        return {
            x:maxX,
            y:maxY
        };
    }

    var graphWidth = window.innerWidth - 80;

    function DependencyGraph(containerElement) {
        this.svg = $(svgElement("svg")).appendTo($(containerElement));
    }

    /* We need custom element creators for svg nodes and xlink attributes because jQuery doesn't support
       namespaces properly */
    function svgElement(name) {
        return document.createElementNS("http://www.w3.org/2000/svg", name);
    }

    function svgLink(url) {
        var element = svgElement("a");
        element.setAttributeNS("http://www.w3.org/1999/xlink", "href", url);
        return element;
    }

    DependencyGraph.prototype.renderGraph = function() {
        var self = this;

        $.each(this.graph.links, function(i, link) {
            var line = $(svgElement("line"))
                        .attr("class","link")
                        .attr("x1", link.source.x)
                        .attr("y1", link.source.y)
                        .attr("x2", link.target.x)
                        .attr("y2", link.target.y)
                        .appendTo(self.svg);
        });

        $.each(this.graph.nodes, function(i, node) {
            var g = $(svgElement("g"))
                .addClass("node")
                .attr("transform", "translate(" + node.x + "," + node.y +")")
                .appendTo(self.svg);

            $(svgElement("circle"))
                .addClass("nodeCircle")
                .attr("r", 7)
                .attr("fill", statusColors[node.status])
                .appendTo(g);
            $(svgLink(node.trackingUrl))
                .append(
                    $(svgElement("text"))
                    .text(node.name)
                    .attr("y", 3))
                .attr("class","graph-node-a")
                .attr("data-task-status", node.status)
                .attr("data-task-id", node.taskId)
                .appendTo(g);

            var titleText = node.name;
            var content = $.map(node.params, function (value, name) { return name + ": " + value; }).join("
");
            g.attr("title", titleText)
                .popover({
                    trigger: 'hover',
                    container: 'body',
                    html: true,
                    placement: 'top',
                    content: content
                });
        });

        // Legend for Task status
        var legend = $(svgElement("g"))
                .addClass("legend")
                .appendTo(self.svg)

        $(svgElement("rect"))
            .attr("x", -1)
            .attr("y", -1)
            .attr("width", "100px")
            .attr("height", legendMaxY + "px")
            .attr("fill", "#FFF")
            .attr("stroke", "#DDD")
            .appendTo(legend);

        var x = 0;
        $.each(statusColors, function(key, color) {
            var c = $(svgElement("circle"))
                .addClass("nodeCircle")
                .attr("r", 7)
                .attr("cx", legendLineHeight)
                .attr("cy", (legendLineHeight-4)+(x*legendLineHeight))
                .attr("fill", color)
                .appendTo(legend)

            $(svgElement("text"))
                .text(key.charAt(0).toUpperCase() + key.substring(1).toLowerCase())
                .attr("x", legendLineHeight + 14)
                .attr("y", legendLineHeight+(x*legendLineHeight))
                .appendTo(legend);

            x++;
        });
    };

    DependencyGraph.prototype.updateData = function(taskList) {
        $('.popover').popover('destroy');
        this.graph = createGraph(taskList);
        bounds = findBounds(this.graph.nodes);
        this.renderGraph();
        this.svg.attr("height", bounds.y+10);
        this.svg.attr("width", graphWidth+10);
        this.svg[0].setAttributeNS("http://www.w3.org/2000/svg", "preserveAspectRatio", "xMidYMid meet");
        this.svg[0].setAttributeNS("http://www.w3.org/2000/svg", "viewBox", "0 0 " + graphWidth + " " + (bounds.y+10));
    };

    return {
        DependencyGraph: DependencyGraph,
        testableMethods: {
            nodeFromTask: nodeFromTask,
            uniqueIndexByProperty: uniqueIndexByProperty,
            createDependencyEdges: createDependencyEdges,
            computeDepth: computeDepth,
            createGraph: createGraph,
            findBounds: findBounds
        }
    };
})();
<|endoftext|>


<commit_before>/**
 * Created by hpb on 7/15/2017.
 */
$(document).ready(function() {
    $('#example1').DataTable({responsive: true});

} );

$(document).on("click", ".open-modal-action", function () {
    var item_id = $(this).data('id');
    $(".modal-body #item-id").val( item_id );

    var item = $(this).data('id');
    $(".modal-body #dist-id").val( item );

    var return_id = $(this).data('id');
    $(".modal-body #return-id").val( return_id );

    var user_id = $(this).data('id');
    $(".modal-body #user-id").val( user_id );
});

var inventory;
var department;
var increaselog;

$(document).ready(function() {

    inventory = $('#table').DataTable({

        destroy: true,
        responsive: true,
        "ajax": {
            "url": 'inventory/inventory_list',
            "type": "POST",
        },
    });

    department = $('#department').DataTable({
        destroy: true,
        responsive: true,
        "ajax": {
            "url":'department/get_all_dept_list',
            "type": "POST",
        },
    });

    $('select[name="department"]').change(function(){
        var id = $(this).val();

        department = $('#department').DataTable({
            responsive: true,
            "destroy": true,
            "ajax": {
                "url":'department/get_dept_list/'+id,
                "type": "POST",
            },
        });
        if (id == 'none') {
            department.column( 0 ).visible( true );
        } else {
            department.column( 0 ).visible( false );
        }

    });

    increaselog = $('#increase_log').DataTable({
        destroy: true,
        responsive: true,
        "ajax": {
            "url": 'increaselog/increase_log_list',
            "type": "POST",
        },
    });

    decreaselog = $('#decrease_log').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'decreaselog/decrease_log_list',
            "type": "POST",
        },
    });

    returnlog = $('#return_log').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'returnlog/return_log_list',
            "type": "POST",
        },
    });

    returned_items = $('#returned_items').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'returned/returned_list',
            "type": "POST",
        },
    });

    pending = $('#pending').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'users/display_pending_users',
            "type": "POST",
        },
    });

    setInterval(function () {
        inventory.ajax.reload(null,false);
        department.ajax.reload(null,false);
        returned_items.ajax.reload(null,false);
        pending.ajax.reload(null,false);
    }, 1000);
});

function summary() {
    $('#departmentsummary').DataTable({
        responsive: true,
        "destroy": true,
        "ajax": {
            "url": 'department/summary_items',
            "type": "POST",
        },
    });
    $('#summary').modal('show');
}

function subtract_quantity(id) {
    $.ajax({
        url: 'inventory/get_quantity/' + id,
        type:"POST",
        dataType: 'json',
        success: function(data){
            $('input[name="Quantity"]').attr('max', data.data);
            $('#subqty').modal('show');
        },
    });

}

function return_no_action(id) {
    $.ajax({
        url: 'returned/no_action/' + id,
        type:"POST",
        success: function() {
            location.href = window.location.origin + "/app/returned"
        }
    });

}
//select all
$(document).ready(function () {
    $('input[name=select-all]').change(function () {
        $('input[name=item-det]').prop('checked',this.checked);
    });
});
function get_item_details(id) {
    var serial;
    var item_det_id = [];
    var oldData;
    //detail list
    details = $('#details').DataTable({
        responsive: true,
        "destroy": true,
        "ajax": {
            "url": 'inventory/itemdetail/' + id,
            "type": "GET",
        },
    });

    //multiple serial input
   $('#details ').on( 'change ',  function () {
           $('tr :nth-child(2)').attr('contentEditable', 'true');
           $(this).focus(function () {
               oldData = $(this).text();
           });

           // input field for serial
       $.each($('#details tr input[name=item-det]'),function () {
               // reset button
               $('input[type=reset]').on('click', function () {
                   $('input[type=number]').val('').change();
               });
               $('input[type=number]').on('keyup change ', function () {
                   serial = ($(this).val());
                   $.each($('#details tr input[name=item-det]:checked'), function () {
                       $(this).parent().siblings(':first').text(serial);
                   });

               });
       });
   });
   //single serial input
       $('#details').on( 'click', 'tr :nth-child(2)', function () {
           $(this).attr('contentEditable', 'true');
           $(this).focus(function () {
               oldData = $(this).text();
           });
           $(this).blur(function () {
               serial = $(this).text();
               if(oldData != serial) {
                   $.ajax({
                       type: "POST",
                       url: 'inventory/set_serial/' + id,
                       data: {'serial':serial},
                       dataType: 'json',
                   });
               }
           });

       });

    $('#view').modal('show');

}

function get_distribution_details(id) {
    $('#details').DataTable({
        responsive: true,
        "destroy": true,
        "ajax": {
            "url": 'department/dist_details/' + id,
            "type": "POST",
        },
    });
    $('#view').modal('show');
}

var save_method; //for save method string

function edit_inventory(id)
{
    save_method = 'update';

    //Ajax Load data from ajax
    $.ajax({
        url : 'inventory/edit/' + id,
        type: "GET",
        dataType: "JSON",
        success: function(data)
        {
            $.each(data, function(i, item) {
                $('[name="item_id"]').val(data[i].item_id);
                $('[name="item_name"]').val(data[i].item_name);
                $('[name="desc"]').val(data[i].item_description);
                $('[name="unit"]').val(data[i].unit);
                $('[name="qty"]').val(data[i].quantity);
            });
            $('#edit').modal('show'); // show bootstrap modal when complete loaded

        },
        error: function (jqXHR, textStatus, errorThrown)
        {
            alert('Error get data from ajax');
        }
    });
}
function save()
{
    var url;
    if(save_method === 'update')

        url = 'admin/edit/item_update';

    // ajax adding data to database
    $.ajax({
        url : url,
        type: "POST",
        data: $('#form').serialize(),
        dataType: "JSON",
        success: function(data)
        {
            //if success close modal and reload ajax table
            $('#edit').modal('hide');
            location.reload();// for reload a page
        },
        error: function (jqXHR, textStatus, errorThrown)
        {
            alert('Error adding / update data');
        }
    });
}
function create_chart()
{
    var chart;
    // Build the chart
    $('#items').highcharts({
        chart: {
            plotBackgroundColor: null,
            plotBorderWidth: null,
            plotShadow: false
        },
        title: {
            text: 'Items'
        },
        tooltip: {
            pointFormat: '{series.name}: {point.percentage:.1f}%'
        },
        plotOptions: {
            pie: {
                allowPointSelect: true,
                cursor: 'pointer',
                dataLabels: {
                    enabled: false
                },
                showInLegend: true
            }
        },
        series: [{
            type: 'pie',
            name: 'Top 5 items',
            data: [
                ['Ballpen',   4],
                ['Table',       2],
                ['Bag',    1],
                ['Paper',     1],
                ['Laptop',   2]
            ]
        }]
    });
}

//form validations
    $(document).ready(function () {

        $( "input[name=Item_Quantity]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 || x === 46 || x === 45 || x === 43){
                return false;
            }
        });
        $( "input[name=Cost]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 ||  x === 45 || x === 43){
                return false;
            }
        });
        $( "input[id=new_qty]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 || x === 46 || x === 45 || x === 43){
                return false;
            }
        });
        $( "input[id=new_cost]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 ||  x === 45 || x === 43){
                return false;
            }
        });
        $( "input[name=datereceived]" ).on("blur",function () {
            del_date = new Date($('input[name=datedelivered]').val());
            date_rec = new Date($('input[name=datereceived]').val());

            if(del_date > date_rec){
                $(this).val('');
            }
        });

        $( "input[name=ExpirationDate]" ).on("blur",function () {
            date_rec = new Date($('input[name=datereceived]').val());
            exp_date = new Date($('input[name=ExpirationDate]').val());

            if(exp_date < date_rec){
                $(this).val('');
            }
        });

    });
//return items

function return_selected_items() {
    var item_det_id = [];
    var reason;
    var person;
    var checked;

    $('#item_detail:checked').each(function () {
        item_det_id.push($(this).val());
    });

    $('#returnmodal').modal('show');

    $('button[id=save1]').on('click',function () {
        reason =  $('textarea#reason').val();
        person =  $('input[name=person]').val();

        if ($('input[name=defect]').is(":checked"))
        {
            checked = 'yes';
        } else {
            checked = 'no';
        }

        var item_data;

        if ($('#item_detail:checked').length > 0 && reason && person) {
            item_data = {'item_det_id':item_det_id,'reason':reason,'person':person, 'ischecked': checked};
        } else {
            return;
        }

        $.ajax({
           url : 'department/return_items',
           type: "POST",
           data: item_data,
           dataType: "JSON",
        });
    });
}

<commit_msg>temporary error message in return<commit_after>/**
 * Created by hpb on 7/15/2017.
 */
$(document).ready(function() {
    $('#example1').DataTable({responsive: true});

} );

$(document).on("click", ".open-modal-action", function () {
    var item_id = $(this).data('id');
    $(".modal-body #item-id").val( item_id );

    var item = $(this).data('id');
    $(".modal-body #dist-id").val( item );

    var return_id = $(this).data('id');
    $(".modal-body #return-id").val( return_id );

    var user_id = $(this).data('id');
    $(".modal-body #user-id").val( user_id );
});

var inventory;
var department;
var increaselog;

$(document).ready(function() {

    inventory = $('#table').DataTable({

        destroy: true,
        responsive: true,
        "ajax": {
            "url": 'inventory/inventory_list',
            "type": "POST",
        },
    });

    department = $('#department').DataTable({
        destroy: true,
        responsive: true,
        "ajax": {
            "url":'department/get_all_dept_list',
            "type": "POST",
        },
    });

    $('select[name="department"]').change(function(){
        var id = $(this).val();

        department = $('#department').DataTable({
            responsive: true,
            "destroy": true,
            "ajax": {
                "url":'department/get_dept_list/'+id,
                "type": "POST",
            },
        });
        if (id == 'none') {
            department.column( 0 ).visible( true );
        } else {
            department.column( 0 ).visible( false );
        }

    });

    increaselog = $('#increase_log').DataTable({
        destroy: true,
        responsive: true,
        "ajax": {
            "url": 'increaselog/increase_log_list',
            "type": "POST",
        },
    });

    decreaselog = $('#decrease_log').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'decreaselog/decrease_log_list',
            "type": "POST",
        },
    });

    returnlog = $('#return_log').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'returnlog/return_log_list',
            "type": "POST",
        },
    });

    returned_items = $('#returned_items').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'returned/returned_list',
            "type": "POST",
        },
    });

    pending = $('#pending').DataTable({
        responsive: true,
        "destroy":true,
        "ajax": {
            "url": 'users/display_pending_users',
            "type": "POST",
        },
    });

    setInterval(function () {
        inventory.ajax.reload(null,false);
        department.ajax.reload(null,false);
        returned_items.ajax.reload(null,false);
        pending.ajax.reload(null,false);
    }, 1000);
});

function summary() {
    $('#departmentsummary').DataTable({
        responsive: true,
        "destroy": true,
        "ajax": {
            "url": 'department/summary_items',
            "type": "POST",
        },
    });
    $('#summary').modal('show');
}

function subtract_quantity(id) {
    $.ajax({
        url: 'inventory/get_quantity/' + id,
        type:"POST",
        dataType: 'json',
        success: function(data){
            $('input[name="Quantity"]').attr('max', data.data);
            $('#subqty').modal('show');
        },
    });

}

function return_no_action(id) {
    $.ajax({
        url: 'returned/no_action/' + id,
        type:"POST",
        success: function() {
            location.href = window.location.origin + "/app/returned"
        }
    });

}
//select all
$(document).ready(function () {
    $('input[name=select-all]').change(function () {
        $('input[name=item-det]').prop('checked',this.checked);
    });
});
function get_item_details(id) {
    var serial;
    var item_det_id = [];
    var oldData;
    //detail list
    details = $('#details').DataTable({
        responsive: true,
        "destroy": true,
        "ajax": {
            "url": 'inventory/itemdetail/' + id,
            "type": "GET",
        },
    });

    //multiple serial input
   $('#details ').on( 'change ',  function () {
           $('tr :nth-child(2)').attr('contentEditable', 'true');
           $(this).focus(function () {
               oldData = $(this).text();
           });

           // input field for serial
       $.each($('#details tr input[name=item-det]'),function () {
               // reset button
               $('input[type=reset]').on('click', function () {
                   $('input[type=number]').val('').change();
               });
               $('input[type=number]').on('keyup change ', function () {
                   serial = ($(this).val());
                   $.each($('#details tr input[name=item-det]:checked'), function () {
                       $(this).parent().siblings(':first').text(serial);
                   });

               });
       });
   });
   //single serial input
       $('#details').on( 'click', 'tr :nth-child(2)', function () {
           $(this).attr('contentEditable', 'true');
           $(this).focus(function () {
               oldData = $(this).text();
           });
           $(this).blur(function () {
               serial = $(this).text();
               if(oldData != serial) {
                   $.ajax({
                       type: "POST",
                       url: 'inventory/set_serial/' + id,
                       data: {'serial':serial},
                       dataType: 'json',
                   });
               }
           });

       });

    $('#view').modal('show');

}

function get_distribution_details(id) {
    $('#details').DataTable({
        responsive: true,
        "destroy": true,
        "ajax": {
            "url": 'department/dist_details/' + id,
            "type": "POST",
        },
    });
    $('#view').modal('show');
}

var save_method; //for save method string

function edit_inventory(id)
{
    save_method = 'update';

    //Ajax Load data from ajax
    $.ajax({
        url : 'inventory/edit/' + id,
        type: "GET",
        dataType: "JSON",
        success: function(data)
        {
            $.each(data, function(i, item) {
                $('[name="item_id"]').val(data[i].item_id);
                $('[name="item_name"]').val(data[i].item_name);
                $('[name="desc"]').val(data[i].item_description);
                $('[name="unit"]').val(data[i].unit);
                $('[name="qty"]').val(data[i].quantity);
            });
            $('#edit').modal('show'); // show bootstrap modal when complete loaded

        },
        error: function (jqXHR, textStatus, errorThrown)
        {
            alert('Error get data from ajax');
        }
    });
}
function save()
{
    var url;
    if(save_method === 'update')

        url = 'admin/edit/item_update';

    // ajax adding data to database
    $.ajax({
        url : url,
        type: "POST",
        data: $('#form').serialize(),
        dataType: "JSON",
        success: function(data)
        {
            //if success close modal and reload ajax table
            $('#edit').modal('hide');
            location.reload();// for reload a page
        },
        error: function (jqXHR, textStatus, errorThrown)
        {
            alert('Error adding / update data');
        }
    });
}
function create_chart()
{
    var chart;
    // Build the chart
    $('#items').highcharts({
        chart: {
            plotBackgroundColor: null,
            plotBorderWidth: null,
            plotShadow: false
        },
        title: {
            text: 'Items'
        },
        tooltip: {
            pointFormat: '{series.name}: {point.percentage:.1f}%'
        },
        plotOptions: {
            pie: {
                allowPointSelect: true,
                cursor: 'pointer',
                dataLabels: {
                    enabled: false
                },
                showInLegend: true
            }
        },
        series: [{
            type: 'pie',
            name: 'Top 5 items',
            data: [
                ['Ballpen',   4],
                ['Table',       2],
                ['Bag',    1],
                ['Paper',     1],
                ['Laptop',   2]
            ]
        }]
    });
}

//form validations
    $(document).ready(function () {

        $( "input[name=Item_Quantity]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 || x === 46 || x === 45 || x === 43){
                return false;
            }
        });
        $( "input[name=Cost]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 ||  x === 45 || x === 43){
                return false;
            }
        });
        $( "input[id=new_qty]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 || x === 46 || x === 45 || x === 43){
                return false;
            }
        });
        $( "input[id=new_cost]" ).on("keypress paste",function () {
            var x = event.charCode;
            if(x === 101 ||  x === 45 || x === 43){
                return false;
            }
        });
        $( "input[name=datereceived]" ).on("blur",function () {
            del_date = new Date($('input[name=datedelivered]').val());
            date_rec = new Date($('input[name=datereceived]').val());

            if(del_date > date_rec){
                $(this).val('');
            }
        });

        $( "input[name=ExpirationDate]" ).on("blur",function () {
            date_rec = new Date($('input[name=datereceived]').val());
            exp_date = new Date($('input[name=ExpirationDate]').val());

            if(exp_date < date_rec){
                $(this).val('');
            }
        });

    });
//return items

function return_selected_items() {
    var item_det_id = [];
    var reason;
    var person;
    var checked;

    $('#item_detail:checked').each(function () {
        item_det_id.push($(this).val());
    });

    $('#returnmodal').modal('show');

    $('button[id=save1]').on('click',function () {
        reason =  $('textarea#reason').val();
        person =  $('input[name=person]').val();

        if ($('input[name=defect]').is(":checked"))
        {
            checked = 'yes';
        } else {
            checked = 'no';
        }

        var item_data;

        if ($('#item_detail:checked').length > 0 && reason && person) {
            item_data = {'item_det_id':item_det_id,'reason':reason,'person':person, 'ischecked': checked};
        } else {
            window.alert("Please check at least 1 item and input all required fields. Try again.");
            return;
        }

        $.ajax({
           url : 'department/return_items',
           type: "POST",
           data: item_data,
           dataType: "JSON",
        });
    });
}

<|endoftext|>