<commit_before>var IEEE_754_BINARY_64_PRECISION = Math.pow(2, 53);
var MAX_PACKET_LENGTH            = Math.pow(2, 24) - 1;
var PacketHeader                 = require('./PacketHeader');

module.exports = Parser;
function Parser(options) {
  options = options || {};

  this._buffer            = new Buffer(0);
  this._longPacketBuffers = [];
  this._offset            = 0;
  this._packetEnd         = null;
  this._packetHeader      = null;
  this._onPacket          = options.onPacket || function() {};
  this._nextPacketNumber  = 0;
  this._encoding          = 'utf-8';
  this._paused            = false;
}

Parser.prototype.write = function(buffer) {
  this.append(buffer);

  while (true) {
    if (this._paused) {
      return;
    }

    if (!this._packetHeader) {
      if (this._bytesRemaining() < 4) {
        break;
      }

      this._packetHeader = new PacketHeader(
        this.parseUnsignedNumber(3),
        this.parseUnsignedNumber(1)
      );

      this._trackAndVerifyPacketNumber(this._packetHeader.number);
    }

    if (this._bytesRemaining() < this._packetHeader.length) {
      break;
    }

    this._packetEnd = this._offset + this._packetHeader.length;

    if (this._packetHeader.length === MAX_PACKET_LENGTH) {
      this._longPacketBuffers.push(this._buffer.slice(this._offset, this._packetEnd));

      this._advanceToNextPacket();
      continue;
    }

    this._combineLongPacketBuffers();

    // Try...finally to ensure exception safety. Unfortunately this is costing
    // us up to ~10% performance in some benchmarks.
    var hadException = true;
    try {
      this._onPacket(this._packetHeader);
      hadException = false;
    } finally {
      this._advanceToNextPacket();

      // If we had an exception, the parser while loop will be broken out
      // of after the finally block. So we need to make sure to re-enter it
      // to continue parsing any bytes that may already have been received.
      if (hadException) {
        process.nextTick(this.write.bind(this));
      }
    }
  }
};

Parser.prototype.append = function(newBuffer) {
  // If resume() is called, we don't pass a buffer to write()
  if (!newBuffer) {
    return;
  }

  var oldBuffer = this._buffer;
  var bytesRemaining = this._bytesRemaining();
  var newLength = bytesRemaining + newBuffer.length;

  var combinedBuffer = (this._offset > newLength)
    ? oldBuffer.slice(0, newLength)
    : new Buffer(newLength);

  oldBuffer.copy(combinedBuffer, 0, this._offset);
  newBuffer.copy(combinedBuffer, bytesRemaining);

  this._buffer = combinedBuffer;
  this._offset = 0;
};

Parser.prototype.pause = function() {
  this._paused = true;
};

Parser.prototype.resume = function() {
  this._paused = false;

  // nextTick() to avoid entering write() multiple times within the same stack
  // which would cause problems as write manipulates the state of the object.
  process.nextTick(this.write.bind(this));
};

Parser.prototype.peak = function() {
  return this._buffer[this._offset];
};

Parser.prototype.parseUnsignedNumber = function(bytes) {
  var bytesRead = 0;
  var value     = 0;

  while (bytesRead < bytes) {
    var byte = this._buffer[this._offset++];

    value += byte * Math.pow(256, bytesRead);

    bytesRead++;
  }

  return value;
};

Parser.prototype.parseLengthCodedString = function() {
  var length = this.parseLengthCodedNumber();

  if (length === null) {
    return null;
  }

  return this.parseString(length);
};

Parser.prototype.parseLengthCodedBuffer = function() {
  var length = this.parseLengthCodedNumber();

  if (length === null) {
    return null;
  }

  return this.parseBuffer(length);
};

Parser.prototype.parseLengthCodedNumber = function() {
  var byte = this._buffer[this._offset++];

  if (byte <= 251) {
    return (byte === 251)
      ? null
      : byte;
  }

  var length;
  if (byte === 252) {
    length = 2;
  } else if (byte === 253) {
    length = 3;
  } else if (byte === 254) {
    length = 8;
  } else {
    throw new Error('parseLengthCodedNumber: Unexpected first byte: ' + byte);
  }

  var value = 0;
  for (var bytesRead = 0; bytesRead < length; bytesRead++) {
    var byte = this._buffer[this._offset++];
    value += Math.pow(256, bytesRead) * byte;
  }

  if (value >= IEEE_754_BINARY_64_PRECISION) {
    throw new Error(
      'parseLengthCodedNumber: JS precision range exceeded, ' +
      'number is >= 53 bit: "' + value + '"'
    );
  }

  return value;
};

Parser.prototype.parseFiller = function(length) {
  return this.parseBuffer(length);
};

Parser.prototype.parseNullTerminatedBuffer = function() {
  var end      = this._nullByteOffset();
  var value    = this._buffer.slice(this._offset, end);
  this._offset = end + 1;

  return value;
};

Parser.prototype.parseNullTerminatedString = function() {
  var end      = this._nullByteOffset();
  var value    = this._buffer.toString(this._encoding, this._offset, end)
  this._offset = end + 1;

  return value;
};

Parser.prototype._nullByteOffset = function() {
  var offset = this._offset;

  while (this._buffer[offset] !== 0x00) {
    offset++;

    if (offset >= this._buffer.length) {
      throw new Error('Offset of null terminated string not found.');
    }
  }

  return offset;
};

Parser.prototype.parsePacketTerminatedString = function() {
  var length = this._packetEnd - this._offset;
  return this.parseString(length);
};

Parser.prototype.parseBuffer = function(length) {
  var response = new Buffer(length);
  this._buffer.copy(response, 0, this._offset, this._offset + length);

  this._offset += length;
  return response;
};

Parser.prototype.parseString = function(length) {
  var offset = this._offset;
  var end = offset + length;
  var value = this._buffer.toString(this._encoding, offset, end);

  this._offset = end;
  return value;
};

Parser.prototype.parseGeometryValue = function() {
  var buffer = this.parseLengthCodedBuffer();
  var offset = 4;

  if (buffer === null) {
    return null;
  }

  function parseGeometry() {
    var result = null;
    var byteOrder = buffer.readUInt8(offset); offset += 1;
    var wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
    switch(wkbType) {
      case 1: // WKBPoint
        var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
        var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
        result = {x: x, y: y};
        break;
      case 2: // WKBLineString
        var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
        result = [];
        for(var i=numPoints;i>0;i--) {
          var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
          var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
          result.push({x: x, y: y});
        }
        break;
      case 3: // WKBPolygon
        var numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
        result = [];
        for(var i=numRings;i>0;i--) {
          var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
          var line = [];
          for(var j=numPoints;j>0;j--) {
            var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
            var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
            line.push({x: x, y: y});
          }
          result.push(line);
        }
        break;
      case 4: // WKBMultiPoint
      case 5: // WKBMultiLineString
      case 6: // WKBMultiPolygon
      case 7: // WKBGeometryCollection
        var num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
        var result = [];
        for(var i=num;i>0;i--) {
          result.push(parseGeometry());
        }
        break;
    }
    return result;
  }
  return parseGeometry();
}

Parser.prototype.reachedPacketEnd = function() {
  return this._offset === this._packetEnd;
};

Parser.prototype._bytesRemaining = function() {
  return this._buffer.length - this._offset;
};

Parser.prototype._trackAndVerifyPacketNumber = function(number) {
  if (number !== this._nextPacketNumber) {
    var err = new Error(
      'Packets out of order. Got: ' + number + ' ' +
      'Expected: ' + this._nextPacketNumber
    );

    err.code = 'PROTOCOL_PACKETS_OUT_OF_ORDER';

    throw err;
  }

  this.incrementPacketNumber();
};

Parser.prototype.incrementPacketNumber = function() {
  var currentPacketNumber = this._nextPacketNumber;
  this._nextPacketNumber = (this._nextPacketNumber + 1) % 256;

  return currentPacketNumber;
};

Parser.prototype.resetPacketNumber = function() {
  this._nextPacketNumber = 0;
};

Parser.prototype.packetLength = function() {
  return this._longPacketBuffers.reduce(function(length, buffer) {
    return length + buffer.length;
  }, this._packetHeader.length);
};

Parser.prototype._combineLongPacketBuffers = function() {
  if (!this._longPacketBuffers.length) {
    return;
  }

  var trailingPacketBytes = this._buffer.length - this._packetEnd;

  var length = this._longPacketBuffers.reduce(function(length, buffer) {
    return length + buffer.length;
  }, this._bytesRemaining());

  var combinedBuffer = new Buffer(length);

  var offset = this._longPacketBuffers.reduce(function(offset, buffer) {
    buffer.copy(combinedBuffer, offset);
    return offset + buffer.length;
  }, 0);

  this._buffer.copy(combinedBuffer, offset, this._offset);

  this._buffer            = combinedBuffer;
  this._longPacketBuffers = [];
  this._offset            = 0;
  this._packetEnd         = this._buffer.length - trailingPacketBytes;
};

Parser.prototype._advanceToNextPacket = function() {
  this._offset       = this._packetEnd;
  this._packetHeader = null;
  this._packetEnd    = null;
};
<commit_msg>Use bignumber.js to process numbers over js precision range<commit_after>var IEEE_754_BINARY_64_PRECISION = Math.pow(2, 53);
var MAX_PACKET_LENGTH            = Math.pow(2, 24) - 1;
var PacketHeader                 = require('./PacketHeader');

module.exports = Parser;
function Parser(options) {
  options = options || {};

  this._buffer            = new Buffer(0);
  this._longPacketBuffers = [];
  this._offset            = 0;
  this._packetEnd         = null;
  this._packetHeader      = null;
  this._onPacket          = options.onPacket || function() {};
  this._nextPacketNumber  = 0;
  this._encoding          = 'utf-8';
  this._paused            = false;
}

Parser.prototype.write = function(buffer) {
  this.append(buffer);

  while (true) {
    if (this._paused) {
      return;
    }

    if (!this._packetHeader) {
      if (this._bytesRemaining() < 4) {
        break;
      }

      this._packetHeader = new PacketHeader(
        this.parseUnsignedNumber(3),
        this.parseUnsignedNumber(1)
      );

      this._trackAndVerifyPacketNumber(this._packetHeader.number);
    }

    if (this._bytesRemaining() < this._packetHeader.length) {
      break;
    }

    this._packetEnd = this._offset + this._packetHeader.length;

    if (this._packetHeader.length === MAX_PACKET_LENGTH) {
      this._longPacketBuffers.push(this._buffer.slice(this._offset, this._packetEnd));

      this._advanceToNextPacket();
      continue;
    }

    this._combineLongPacketBuffers();

    // Try...finally to ensure exception safety. Unfortunately this is costing
    // us up to ~10% performance in some benchmarks.
    var hadException = true;
    try {
      this._onPacket(this._packetHeader);
      hadException = false;
    } finally {
      this._advanceToNextPacket();

      // If we had an exception, the parser while loop will be broken out
      // of after the finally block. So we need to make sure to re-enter it
      // to continue parsing any bytes that may already have been received.
      if (hadException) {
        process.nextTick(this.write.bind(this));
      }
    }
  }
};

Parser.prototype.append = function(newBuffer) {
  // If resume() is called, we don't pass a buffer to write()
  if (!newBuffer) {
    return;
  }

  var oldBuffer = this._buffer;
  var bytesRemaining = this._bytesRemaining();
  var newLength = bytesRemaining + newBuffer.length;

  var combinedBuffer = (this._offset > newLength)
    ? oldBuffer.slice(0, newLength)
    : new Buffer(newLength);

  oldBuffer.copy(combinedBuffer, 0, this._offset);
  newBuffer.copy(combinedBuffer, bytesRemaining);

  this._buffer = combinedBuffer;
  this._offset = 0;
};

Parser.prototype.pause = function() {
  this._paused = true;
};

Parser.prototype.resume = function() {
  this._paused = false;

  // nextTick() to avoid entering write() multiple times within the same stack
  // which would cause problems as write manipulates the state of the object.
  process.nextTick(this.write.bind(this));
};

Parser.prototype.peak = function() {
  return this._buffer[this._offset];
};

Parser.prototype.parseUnsignedNumber = function(bytes) {
  var bytesRead = 0;
  var value     = 0;

  while (bytesRead < bytes) {
    var byte = this._buffer[this._offset++];

    value += byte * Math.pow(256, bytesRead);

    bytesRead++;
  }

  return value;
};

Parser.prototype.parseLengthCodedString = function() {
  var length = this.parseLengthCodedNumber();

  if (length === null) {
    return null;
  }

  return this.parseString(length);
};

Parser.prototype.parseLengthCodedBuffer = function() {
  var length = this.parseLengthCodedNumber();

  if (length === null) {
    return null;
  }

  return this.parseBuffer(length);
};

Parser.prototype.parseLengthCodedNumber = function() {
  var byte = this._buffer[this._offset++];

  if (byte <= 251) {
    return (byte === 251)
      ? null
      : byte;
  }

  var length;
  if (byte === 252) {
    length = 2;
  } else if (byte === 253) {
    length = 3;
  } else if (byte === 254) {
    length = 8;
  } else {
    throw new Error('parseLengthCodedNumber: Unexpected first byte: ' + byte);
  }

  var value = 0, bigint = false, BigNumber = null;
  for (var bytesRead = 0; bytesRead < length; bytesRead++) {
    var byte = this._buffer[this._offset++];

    // overflow
    if (BigNumber) {
      value = value.plus((new BigNumber(256)).pow(bytesRead).times(byte));
    } else if (bytesRead == 7 && byte == 1) {
      BigNumber = require("bignumber.js");
      value = new BigNumber(value);
      value = value.plus((new BigNumber(256)).pow(bytesRead).times(byte));
    } else {
      value += Math.pow(256, bytesRead) * byte;
    }
  }

  return (BigNumber ? value.toString() : value);
};

Parser.prototype.parseFiller = function(length) {
  return this.parseBuffer(length);
};

Parser.prototype.parseNullTerminatedBuffer = function() {
  var end      = this._nullByteOffset();
  var value    = this._buffer.slice(this._offset, end);
  this._offset = end + 1;

  return value;
};

Parser.prototype.parseNullTerminatedString = function() {
  var end      = this._nullByteOffset();
  var value    = this._buffer.toString(this._encoding, this._offset, end)
  this._offset = end + 1;

  return value;
};

Parser.prototype._nullByteOffset = function() {
  var offset = this._offset;

  while (this._buffer[offset] !== 0x00) {
    offset++;

    if (offset >= this._buffer.length) {
      throw new Error('Offset of null terminated string not found.');
    }
  }

  return offset;
};

Parser.prototype.parsePacketTerminatedString = function() {
  var length = this._packetEnd - this._offset;
  return this.parseString(length);
};

Parser.prototype.parseBuffer = function(length) {
  var response = new Buffer(length);
  this._buffer.copy(response, 0, this._offset, this._offset + length);

  this._offset += length;
  return response;
};

Parser.prototype.parseString = function(length) {
  var offset = this._offset;
  var end = offset + length;
  var value = this._buffer.toString(this._encoding, offset, end);

  this._offset = end;
  return value;
};

Parser.prototype.parseGeometryValue = function() {
  var buffer = this.parseLengthCodedBuffer();
  var offset = 4;

  if (buffer === null) {
    return null;
  }

  function parseGeometry() {
    var result = null;
    var byteOrder = buffer.readUInt8(offset); offset += 1;
    var wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
    switch(wkbType) {
      case 1: // WKBPoint
        var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
        var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
        result = {x: x, y: y};
        break;
      case 2: // WKBLineString
        var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
        result = [];
        for(var i=numPoints;i>0;i--) {
          var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
          var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
          result.push({x: x, y: y});
        }
        break;
      case 3: // WKBPolygon
        var numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
        result = [];
        for(var i=numRings;i>0;i--) {
          var numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
          var line = [];
          for(var j=numPoints;j>0;j--) {
            var x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
            var y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8;
            line.push({x: x, y: y});
          }
          result.push(line);
        }
        break;
      case 4: // WKBMultiPoint
      case 5: // WKBMultiLineString
      case 6: // WKBMultiPolygon
      case 7: // WKBGeometryCollection
        var num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4;
        var result = [];
        for(var i=num;i>0;i--) {
          result.push(parseGeometry());
        }
        break;
    }
    return result;
  }
  return parseGeometry();
}

Parser.prototype.reachedPacketEnd = function() {
  return this._offset === this._packetEnd;
};

Parser.prototype._bytesRemaining = function() {
  return this._buffer.length - this._offset;
};

Parser.prototype._trackAndVerifyPacketNumber = function(number) {
  if (number !== this._nextPacketNumber) {
    var err = new Error(
      'Packets out of order. Got: ' + number + ' ' +
      'Expected: ' + this._nextPacketNumber
    );

    err.code = 'PROTOCOL_PACKETS_OUT_OF_ORDER';

    throw err;
  }

  this.incrementPacketNumber();
};

Parser.prototype.incrementPacketNumber = function() {
  var currentPacketNumber = this._nextPacketNumber;
  this._nextPacketNumber = (this._nextPacketNumber + 1) % 256;

  return currentPacketNumber;
};

Parser.prototype.resetPacketNumber = function() {
  this._nextPacketNumber = 0;
};

Parser.prototype.packetLength = function() {
  return this._longPacketBuffers.reduce(function(length, buffer) {
    return length + buffer.length;
  }, this._packetHeader.length);
};

Parser.prototype._combineLongPacketBuffers = function() {
  if (!this._longPacketBuffers.length) {
    return;
  }

  var trailingPacketBytes = this._buffer.length - this._packetEnd;

  var length = this._longPacketBuffers.reduce(function(length, buffer) {
    return length + buffer.length;
  }, this._bytesRemaining());

  var combinedBuffer = new Buffer(length);

  var offset = this._longPacketBuffers.reduce(function(offset, buffer) {
    buffer.copy(combinedBuffer, offset);
    return offset + buffer.length;
  }, 0);

  this._buffer.copy(combinedBuffer, offset, this._offset);

  this._buffer            = combinedBuffer;
  this._longPacketBuffers = [];
  this._offset            = 0;
  this._packetEnd         = this._buffer.length - trailingPacketBytes;
};

Parser.prototype._advanceToNextPacket = function() {
  this._offset       = this._packetEnd;
  this._packetHeader = null;
  this._packetEnd    = null;
};
<|endoftext|>


<commit_before>iD.ui.RadialMenu = function(operations) {
    var menu,
        center = [0, 0];

    var radialMenu = function(selection) {
        if (!operations.length)
            return;

        function click(operation) {
            d3.event.stopPropagation();
            operation();
        }

        menu = selection.append('g')
            .attr('class', 'radial-menu')
            .attr('transform', "translate(" + center + ")")
            .attr('opacity', 0);

        menu.transition()
            .attr('opacity', 1);

        var r = 50,
            a = Math.PI / 4,
            a0 = -Math.PI / 4,
            a1 = a0 + (operations.length - 1) * a;

        menu.append('path')
            .attr('class', 'radial-menu-background')
            .attr('d', 'M' + r * Math.sin(a0) + ',' +
                             r * Math.cos(a0) +
                      ' A' + r + ',' + r + ' 0 0,0 ' +
                             r * Math.sin(a1) + ',' +
                             r * Math.cos(a1))
            .attr('stroke-width', 50)
            .attr('stroke-linecap', 'round');

        var button = menu.selectAll()
            .data(operations)
            .enter().append('g')
            .attr('transform', function(d, i) {
                return 'translate(' + r * Math.sin(a0 + i * a) + ',' +
                                      r * Math.cos(a0 + i * a) + ')';
            });

        button.append('circle')
            .attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; })
            .attr('r', 15)
            .attr('title', function(d) { return d.title; })
            .classed('disabled', function(d) { return !d.enabled(); })
            .on('click', click)
            .on('mouseover', mouseover)
            .on('mouseout', mouseout);

        var image = button.append('foreignObject')
            .style('pointer-events', 'none')
            .attr('width', 20)
            .attr('height', 20)
            .attr('x', -10)
            .attr('y', -10);

        image.append('xhtml:span')
            .attr('class', function(d) { return 'icon icon-operation icon-operation-' + d.id; });

        var tooltip = menu.append('foreignObject')
            .style('display', 'none')
            .attr('width', 200)
            .attr('height', 400);

        tooltip.append('xhtml:div')
            .attr('class', 'radial-menu-tooltip tooltip-inner');

        function mouseover(d, i) {
            var angle = a0 + i * a,
                dx = angle < 0 ? -200 : 0,
                dy = 0;

            tooltip
                .attr('x', (r + 25) * Math.sin(angle) + dx)
                .attr('y', (r + 25) * Math.cos(angle) + dy)
                .style('display', 'block')
                .select('div')
                .text(d.description);
        }

        function mouseout() {
            tooltip.style('display', 'none');
        }
    };

    radialMenu.close = function(selection) {
        if (menu) {
            menu.transition()
                .attr('opacity', 0)
                .remove();
        }
    };

    radialMenu.center = function(_) {
        if (!arguments.length) return center;
        center = _;
        return radialMenu;
    };

    return radialMenu;
};
<commit_msg>Since we have custom tooltips, we don't need title<commit_after>iD.ui.RadialMenu = function(operations) {
    var menu,
        center = [0, 0];

    var radialMenu = function(selection) {
        if (!operations.length)
            return;

        function click(operation) {
            d3.event.stopPropagation();
            operation();
        }

        menu = selection.append('g')
            .attr('class', 'radial-menu')
            .attr('transform', "translate(" + center + ")")
            .attr('opacity', 0);

        menu.transition()
            .attr('opacity', 1);

        var r = 50,
            a = Math.PI / 4,
            a0 = -Math.PI / 4,
            a1 = a0 + (operations.length - 1) * a;

        menu.append('path')
            .attr('class', 'radial-menu-background')
            .attr('d', 'M' + r * Math.sin(a0) + ',' +
                             r * Math.cos(a0) +
                      ' A' + r + ',' + r + ' 0 0,0 ' +
                             r * Math.sin(a1) + ',' +
                             r * Math.cos(a1))
            .attr('stroke-width', 50)
            .attr('stroke-linecap', 'round');

        var button = menu.selectAll()
            .data(operations)
            .enter().append('g')
            .attr('transform', function(d, i) {
                return 'translate(' + r * Math.sin(a0 + i * a) + ',' +
                                      r * Math.cos(a0 + i * a) + ')';
            });

        button.append('circle')
            .attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; })
            .attr('r', 15)
            .classed('disabled', function(d) { return !d.enabled(); })
            .on('click', click)
            .on('mouseover', mouseover)
            .on('mouseout', mouseout);

        var image = button.append('foreignObject')
            .style('pointer-events', 'none')
            .attr('width', 20)
            .attr('height', 20)
            .attr('x', -10)
            .attr('y', -10);

        image.append('xhtml:span')
            .attr('class', function(d) { return 'icon icon-operation icon-operation-' + d.id; });

        var tooltip = menu.append('foreignObject')
            .style('display', 'none')
            .attr('width', 200)
            .attr('height', 400);

        tooltip.append('xhtml:div')
            .attr('class', 'radial-menu-tooltip tooltip-inner');

        function mouseover(d, i) {
            var angle = a0 + i * a,
                dx = angle < 0 ? -200 : 0,
                dy = 0;

            tooltip
                .attr('x', (r + 25) * Math.sin(angle) + dx)
                .attr('y', (r + 25) * Math.cos(angle) + dy)
                .style('display', 'block')
                .select('div')
                .text(d.description);
        }

        function mouseout() {
            tooltip.style('display', 'none');
        }
    };

    radialMenu.close = function(selection) {
        if (menu) {
            menu.transition()
                .attr('opacity', 0)
                .remove();
        }
    };

    radialMenu.center = function(_) {
        if (!arguments.length) return center;
        center = _;
        return radialMenu;
    };

    return radialMenu;
};
<|endoftext|>


<commit_before>//
// HLF Hover Intent Extension
// ==========================
// [Tests](../../tests/js/hover-intent.html)
//
// The `hoverIntent` extension normalizes DOM events associated with mouse enter
// and leave interaction. It prevents the 'thrashing' of attached behaviors (ex:
// non-cancel-able animations) when matching mouse input arrives at frequencies
// past the threshold. It is heavily inspired by Brian Cherne's jQuery plugin of
// the same name (github.com/briancherne/jquery-hoverIntent).
(function(root, attach) {
  //
  // § __UMD__
  // - When AMD, register the attacher as an anonymous module.
  // - When Node or Browserify, set module exports to the attach result.
  // - When browser globals (root is window), Just run the attach function.
  //
  if (typeof define === 'function' && define.amd) {
    define(['hlf/core'], attach);
  } else if (typeof exports === 'object') {
    module.exports = attach(require('hlf/core'));
  } else {
    attach(hlf);
  }
})(this, function(hlf) {
  //
  // Namespace
  // ---------
  //
  // It takes some more boilerplate to write the extension. Any of this additional
  // support API is put into a extension specific namespace under `hlf`, which in
  // this case is __hlf.hoverIntent__.
  //
  // - __debug__ toggles debug logging for all instances of an extension.
  // - __toString__ helps to namespace when registering any DOM names.
  // - __attrName__, __className__, __eventName__ helpers are all attached to
  //   the namespace on extension creation, along with the __extension__ itself.
  //
  // The extension's __defaults__ are available as reference. Also note that _the
  // extension instance gets extended with the options_.
  //
  // - __interval__ is the millis to wait before deciding intent. `300` is the
  //   default to reduce more thrashing.
  //
  // - __sensitivity__ is the pixel threshold for mouse travel between polling
  //   intervals. With the minimum sensitivity threshold of 1, the mouse must
  //   not move between intervals. With higher values yield more false positives.
  //   `6` is the default.
  //
  // The events dispatched are the name-spaced `enter` and `leave` events. They
  // try to match system mouse events where possible and include values for:
  // `pageX`, `pageY`, `relatedTarget`.
  //
  hlf.hoverIntent = {
    debug: true,
    defaults: {
      interval: 300,
      sensitivity: 6,
    },
    toString(context) {
      switch (context) {
        case 'event': return 'hlfhi';
        case 'data': return 'hlf-hi';
        default: return 'hlf-hi';
      }
    },
  };
  //
  // HoverIntent
  // -----------
  //
  // To summarize the implementation, the `_onMouseOver` handler is the start of
  // the intent 'life-cycle', with state being the default. It records the mouse
  // coordinates, sets up a delayed intent check. All event handlers guard
  // against unneeded checking, with `_onMouseOut` and `_onMouseOver` also using
  // `_checkEventElement` as a filter. Meanwhile, about every frame, the
  // `_onMouseMove` handler tracks the current mouse coordinates. If the
  // `_onMouseOut` handler runs before the delayed check, the check and
  // subsequent behavior get canceled as state resets to default. Otherwise, if
  // the check of the stored mouse coordinates against `sensitivity` passes, an
  // `enter` event gets dispatched, ensuring a `leave` event will too during
  // `_onMouseOut`.
  //
  class HoverIntent {
    constructor(elementOrElements, options, contextElement) {
      this.eventListeners = {
        'mousemove': this._onMouseMove,
        'mouseout': this._onMouseOut,
        'mouseover': this._onMouseOver,
      };
    }
    init() {
      this._resetState();
    }
    deinit() {
      this._resetState();
    }
    //
    // § __Internal__
    //
    _checkEventElement(event) {
      const { relatedTarget, target, type } = event;
      if (type === 'mouseout' && target.contains(relatedTarget)) {
        return false;
      }
      if (this.contextElement) {
        if (Array.from(this.elements).indexOf(target) === -1) { return false; }
        // this.debugLog(target, relatedTarget);
      } else {
        if (target !== this.rootElement) { return false; }
      }
      return true;
    }
    _dispatchHoverEvent(on, event) {
      const { mouse: { x, y } } = this;
      let type = on ? 'enter' : 'leave';
      event.target.dispatchEvent(this.createCustomEvent(type, {
        pageX: x.current,
        pageY: y.current,
        relatedTarget: event.relatedTarget,
      }));
      this.debugLog(type, x.current, y.current, Date.now() % 100000);
    }
    _dispatchTrackEvent(event) {
      event.target.dispatchEvent(this.createCustomEvent('track', {
        pageX: event.pageX,
        pageY: event.pageY,
      }));
    }
    _onMouseMove(event) {
      if (this._trackTimeout) { return; }
      if (!this.intentional) { return; }
      this.setTimeout('_trackTimeout', 16, () => {
        this._updateState(event);
        this._dispatchTrackEvent(event);
      });
    }
    _onMouseOut(event) {
      if (!this._checkEventElement(event)) { return; }
      if (this.intentional) {
        this._dispatchHoverEvent(false, event);
      }
      this._resetState();
      this.debugLogGroup(false);
    }
    _onMouseOver(event) {
      if (this.intentional) { return; }
      if (this._timeout) { return; }
      if (!this._checkEventElement(event)) { return; }
      this.debugLogGroup();
      this._updateState(event);
      this.setTimeout('_timeout', this.interval, () => {
        this._updateState(event);
        if (this.intentional) {
          this._dispatchHoverEvent(true, event);
        }
      });
    }
    _resetState() {
      this.debugLog('reset');
      this.intentional = false;
      this.mouse = {
        x: { current: 0, previous: 0 },
        y: { current: 0, previous: 0 },
      };
      this.setTimeout('_timeout', null);
      this.setTimeout('_trackTimeout', null);
    }
    _updateState(event) {
      if (event.type === 'mousemove') {
        this.debugLog('track');
        let { mouse: { x, y } } = this;
        x.current = event.pageX;
        y.current = event.pageY;
        return;
      }
      let { mouse: { x, y } } = this;
      if (!this._timeout) {
        x.previous = event.pageX;
        y.previous = event.pageY;
        return;
      }
      this.debugLog('check');
      const { abs, pow, sqrt } = Math;
      this.intentional = (
        abs(
          sqrt(pow(x.previous, 2) + pow(y.previous, 2)) -
          sqrt(pow(x.current, 2) + pow(y.current, 2))
        ) > this.sensitivity
      );
    }
  }
  //
  // § __Attaching__
  //
  return hlf.createExtension({
    name: 'hoverIntent',
    namespace: hlf.hoverIntent,
    apiClass: HoverIntent,
    autoBind: true,
    autoListen: true,
    compactOptions: true,
  });
});
<commit_msg>Continue tip port: fix-up hover-intent position.<commit_after>//
// HLF Hover Intent Extension
// ==========================
// [Tests](../../tests/js/hover-intent.html)
//
// The `hoverIntent` extension normalizes DOM events associated with mouse enter
// and leave interaction. It prevents the 'thrashing' of attached behaviors (ex:
// non-cancel-able animations) when matching mouse input arrives at frequencies
// past the threshold. It is heavily inspired by Brian Cherne's jQuery plugin of
// the same name (github.com/briancherne/jquery-hoverIntent).
(function(root, attach) {
  //
  // § __UMD__
  // - When AMD, register the attacher as an anonymous module.
  // - When Node or Browserify, set module exports to the attach result.
  // - When browser globals (root is window), Just run the attach function.
  //
  if (typeof define === 'function' && define.amd) {
    define(['hlf/core'], attach);
  } else if (typeof exports === 'object') {
    module.exports = attach(require('hlf/core'));
  } else {
    attach(hlf);
  }
})(this, function(hlf) {
  //
  // Namespace
  // ---------
  //
  // It takes some more boilerplate to write the extension. Any of this additional
  // support API is put into a extension specific namespace under `hlf`, which in
  // this case is __hlf.hoverIntent__.
  //
  // - __debug__ toggles debug logging for all instances of an extension.
  // - __toString__ helps to namespace when registering any DOM names.
  // - __attrName__, __className__, __eventName__ helpers are all attached to
  //   the namespace on extension creation, along with the __extension__ itself.
  //
  // The extension's __defaults__ are available as reference. Also note that _the
  // extension instance gets extended with the options_.
  //
  // - __interval__ is the millis to wait before deciding intent. `300` is the
  //   default to reduce more thrashing.
  //
  // - __sensitivity__ is the pixel threshold for mouse travel between polling
  //   intervals. With the minimum sensitivity threshold of 1, the mouse must
  //   not move between intervals. With higher values yield more false positives.
  //   `6` is the default.
  //
  // The events dispatched are the name-spaced `enter` and `leave` events. They
  // try to match system mouse events where possible and include values for:
  // `pageX`, `pageY`, `relatedTarget`.
  //
  hlf.hoverIntent = {
    debug: true,
    defaults: {
      interval: 300,
      sensitivity: 6,
    },
    toString(context) {
      switch (context) {
        case 'event': return 'hlfhi';
        case 'data': return 'hlf-hi';
        default: return 'hlf-hi';
      }
    },
  };
  //
  // HoverIntent
  // -----------
  //
  // To summarize the implementation, the `_onMouseOver` handler is the start of
  // the intent 'life-cycle', with state being the default. It records the mouse
  // coordinates, sets up a delayed intent check. All event handlers guard
  // against unneeded checking, with `_onMouseOut` and `_onMouseOver` also using
  // `_checkEventElement` as a filter. Meanwhile, about every frame, the
  // `_onMouseMove` handler tracks the current mouse coordinates. If the
  // `_onMouseOut` handler runs before the delayed check, the check and
  // subsequent behavior get canceled as state resets to default. Otherwise, if
  // the check of the stored mouse coordinates against `sensitivity` passes, an
  // `enter` event gets dispatched, ensuring a `leave` event will too during
  // `_onMouseOut`.
  //
  class HoverIntent {
    constructor(elementOrElements, options, contextElement) {
      this.eventListeners = {
        'mousemove': this._onMouseMove,
        'mouseout': this._onMouseOut,
        'mouseover': this._onMouseOver,
      };
    }
    init() {
      this._resetState();
    }
    deinit() {
      this._resetState();
    }
    //
    // § __Internal__
    //
    _checkEventElement(event) {
      const { relatedTarget, target, type } = event;
      if (type === 'mouseout' && target.contains(relatedTarget)) {
        return false;
      }
      if (this.contextElement) {
        if (Array.from(this.elements).indexOf(target) === -1) { return false; }
        // this.debugLog(target, relatedTarget);
      } else {
        if (target !== this.rootElement) { return false; }
      }
      return true;
    }
    _dispatchHoverEvent(on, event) {
      const { mouse: { x, y } } = this;
      let type = on ? 'enter' : 'leave';
      event.target.dispatchEvent(this.createCustomEvent(type, {
        pageX: event.pageX,
        pageY: event.pageY,
        relatedTarget: event.relatedTarget,
      }));
      this.debugLog(type, event.pageX, event.pageY, Date.now() % 100000);
    }
    _dispatchTrackEvent(event) {
      event.target.dispatchEvent(this.createCustomEvent('track', {
        pageX: event.pageX,
        pageY: event.pageY,
      }));
    }
    _onMouseMove(event) {
      if (this._trackTimeout) { return; }
      if (!this.intentional) { return; }
      this.setTimeout('_trackTimeout', 16, () => {
        this._updateState(event);
        this._dispatchTrackEvent(event);
      });
    }
    _onMouseOut(event) {
      if (!this._checkEventElement(event)) { return; }
      if (this.intentional) {
        this._dispatchHoverEvent(false, event);
      }
      this._resetState();
      this.debugLogGroup(false);
    }
    _onMouseOver(event) {
      if (this.intentional) { return; }
      if (this._timeout) { return; }
      if (!this._checkEventElement(event)) { return; }
      this.debugLogGroup();
      this._updateState(event);
      this.setTimeout('_timeout', this.interval, () => {
        this._updateState(event);
        if (this.intentional) {
          this._dispatchHoverEvent(true, event);
        }
      });
    }
    _resetState() {
      this.debugLog('reset');
      this.intentional = false;
      this.mouse = {
        x: { current: 0, previous: 0 },
        y: { current: 0, previous: 0 },
      };
      this.setTimeout('_timeout', null);
      this.setTimeout('_trackTimeout', null);
    }
    _updateState(event) {
      if (event.type === 'mousemove') {
        this.debugLog('track');
        let { mouse: { x, y } } = this;
        x.current = event.pageX;
        y.current = event.pageY;
        return;
      }
      let { mouse: { x, y } } = this;
      if (!this._timeout) {
        x.previous = event.pageX;
        y.previous = event.pageY;
        return;
      }
      this.debugLog('check');
      const { abs, pow, sqrt } = Math;
      this.intentional = (
        abs(
          sqrt(pow(x.previous, 2) + pow(y.previous, 2)) -
          sqrt(pow(x.current, 2) + pow(y.current, 2))
        ) > this.sensitivity
      );
    }
  }
  //
  // § __Attaching__
  //
  return hlf.createExtension({
    name: 'hoverIntent',
    namespace: hlf.hoverIntent,
    apiClass: HoverIntent,
    autoBind: true,
    autoListen: true,
    compactOptions: true,
  });
});
<|endoftext|>


<commit_before>Monocle.Controls.Spinner = function (reader) {

  var API = { constructor: Monocle.Controls.Spinner }
  var k = API.constants = API.constructor;
  var p = API.properties = {
    reader: reader,
    divs: [],
    repeaters: {},
    showForPages: []
  }

  function createControlElements(cntr) {
    var anim = cntr.dom.make('div', 'controls_spinner_anim');
    p.divs.push(anim);
    return anim;
  }

  function registerSpinEvent(startEvtType, stopEvtType) {
    var label = startEvtType;
    p.reader.listen(startEvtType, function (evt) { spin(label, evt) });
    p.reader.listen(stopEvtType, function (evt) { spun(label, evt) });
  }

  // Registers spin/spun event handlers for certain time-consuming events.
  //
  function listenForUsualDelays() {
    registerSpinEvent('monocle:componentloading', 'monocle:componentloaded');
    registerSpinEvent('monocle:componentchanging', 'monocle:componentchange');
    registerSpinEvent('monocle:resizing', 'monocle:resize');
    registerSpinEvent('monocle:jumping', 'monocle:jump');
    registerSpinEvent('monocle:recalculating', 'monocle:recalculated');
    p.reader.listen('monocle:notfound', forceSpun);
    p.reader.listen('monocle:componentfailed', forceSpun);
  }

  // Displays the spinner. Both arguments are optional.
  //
  function spin(label, evt) {
    label = label || k.GENERIC_LABEL;
    p.repeaters[label] = true;
    p.reader.showControl(API);
    p.reader.dispatchEvent('monocle:modal:on');

    // If the delay is on a page other than the page we've been assigned to,
    // don't show the animation. p.global ensures that if an event affects
    // all pages, the animation is always shown, even if other events in this
    // spin cycle are page-specific.
    var page = (evt && evt.m && evt.m.page) ? evt.m.page : null;
    if (page && p.divs.length > 1) {
      p.showForPages[page.m.pageIndex] = true;
    } else {
      p.global = true;
    }
    for (var i = 0; i < p.divs.length; ++i) {
      var show = (p.global || p.showForPages[i]) ? true : false;
      p.divs[i].dom[show ? 'removeClass' : 'addClass']('dormant');
    }
  }

  // Stops displaying the spinner. Both arguments are optional.
  //
  function spun(label, evt) {
    label = label || k.GENERIC_LABEL;
    p.repeaters[label] = false;
    for (var l in p.repeaters) {
      if (p.repeaters[l]) { return; }
    }
    forceSpun();
  }

  function forceSpun() {
    p.global = false;
    p.repeaters = {};
    p.showForPages = [];
    for (var i = 0; i < p.divs.length; ++i) {
      p.divs[i].dom.addClass('dormant');
    }
    p.reader.dispatchEvent('monocle:modal:off');
  }

  API.createControlElements = createControlElements;
  API.registerSpinEvent = registerSpinEvent;
  API.listenForUsualDelays = listenForUsualDelays;
  API.spin = spin;
  API.spun = spun;
  API.forceSpun = forceSpun;

  return API;
}

Monocle.Controls.Spinner.GENERIC_LABEL = "generic";
<commit_msg>Spinner should only dispatch modal if global.<commit_after>Monocle.Controls.Spinner = function (reader) {

  var API = { constructor: Monocle.Controls.Spinner }
  var k = API.constants = API.constructor;
  var p = API.properties = {
    reader: reader,
    divs: [],
    repeaters: {},
    showForPages: []
  }

  function createControlElements(cntr) {
    var anim = cntr.dom.make('div', 'controls_spinner_anim');
    p.divs.push(anim);
    return anim;
  }

  function registerSpinEvent(startEvtType, stopEvtType) {
    var label = startEvtType;
    p.reader.listen(startEvtType, function (evt) { spin(label, evt) });
    p.reader.listen(stopEvtType, function (evt) { spun(label, evt) });
  }

  // Registers spin/spun event handlers for certain time-consuming events.
  //
  function listenForUsualDelays() {
    registerSpinEvent('monocle:componentloading', 'monocle:componentloaded');
    registerSpinEvent('monocle:componentchanging', 'monocle:componentchange');
    registerSpinEvent('monocle:resizing', 'monocle:resize');
    registerSpinEvent('monocle:jumping', 'monocle:jump');
    registerSpinEvent('monocle:recalculating', 'monocle:recalculated');
    p.reader.listen('monocle:notfound', forceSpun);
    p.reader.listen('monocle:componentfailed', forceSpun);
  }

  // Displays the spinner. Both arguments are optional.
  //
  function spin(label, evt) {
    label = label || k.GENERIC_LABEL;
    p.repeaters[label] = true;
    p.reader.showControl(API);

    // If the delay is on a page other than the page we've been assigned to,
    // don't show the animation. p.global ensures that if an event affects
    // all pages, the animation is always shown, even if other events in this
    // spin cycle are page-specific.
    var page = (evt && evt.m && evt.m.page) ? evt.m.page : null;
    if (page && p.divs.length > 1) {
      p.showForPages[page.m.pageIndex] = true;
    } else {
      p.global = true;
      p.reader.dispatchEvent('monocle:modal:on');
    }
    for (var i = 0; i < p.divs.length; ++i) {
      var show = (p.global || p.showForPages[i]) ? true : false;
      p.divs[i].dom[show ? 'removeClass' : 'addClass']('dormant');
    }
  }

  // Stops displaying the spinner. Both arguments are optional.
  //
  function spun(label, evt) {
    label = label || k.GENERIC_LABEL;
    p.repeaters[label] = false;
    for (var l in p.repeaters) {
      if (p.repeaters[l]) { return; }
    }
    forceSpun();
  }

  function forceSpun() {
    if (p.global) { p.reader.dispatchEvent('monocle:modal:off'); }
    p.global = false;
    p.repeaters = {};
    p.showForPages = [];
    for (var i = 0; i < p.divs.length; ++i) {
      p.divs[i].dom.addClass('dormant');
    }
  }

  API.createControlElements = createControlElements;
  API.registerSpinEvent = registerSpinEvent;
  API.listenForUsualDelays = listenForUsualDelays;
  API.spin = spin;
  API.spun = spun;
  API.forceSpun = forceSpun;

  return API;
}

Monocle.Controls.Spinner.GENERIC_LABEL = "generic";
<|endoftext|>


<commit_before>/**
 * Package dependencies
 */
const base64url = require('base64url')
const crypto = require('crypto')
const {spawnSync} = require('child_process')
const {TextEncoder, TextDecoder} = require('text-encoding')
const keyto = require('@trust/keyto')

/**
 * Local dependencies
 */
const Algorithm = require ('../algorithms/Algorithm')
const CryptoKey = require('../keys/CryptoKey')
const CryptoKeyPair = require('../keys/CryptoKeyPair')
const JsonWebKey = require('../keys/JsonWebKey')
const KeyAlgorithm = require('../dictionaries/KeyAlgorithm')
const EcKeyAlgorithm = require('../dictionaries/EcKeyAlgorithm')

/**
 * Errors
 */
const {
  DataError,
  OperationError,
  InvalidAccessError,
  KeyFormatNotSupportedError,
  CurrentlyNotSupportedError
} = require('../errors')

/**
 * ECDSA
 */
class ECDSA extends Algorithm {

  /**
   * dictionaries
   */
  static get dictionaries () {
    return [
      KeyAlgorithm,
      EcKeyAlgorithm
    ]
  }

  /**
   * members
   */
  static get members () {
    return {
      name: String,
      namedCurve: String,
      hash: 'HashAlgorithmIdentifier'
    }
  }

  /**
   * sign
   *
   * @description
   * Create a digital signature
   *
   * @param {CryptoKey} key
   * @param {BufferSource} data
   *
   * @returns {ArrayBuffer}
   */
  sign (key, data) {
    let result
    // 1. Ensure the key is a private type only
    if (key.type !== 'private') {
      throw new InvalidAccessError('Signing requires a private key')
    }
    // 2-5. Ommitted due to support by Crypto
    // 6. Attempt to sign using Crypto lib
    // try {
        data = Buffer.from(data)

        let signer = crypto.createSign('sha256')
        signer.update(data)

        result = signer.sign(key.handle)
    // } catch (error) {
    //   throw new OperationError(error.message)
    // }
    // 7. Return resulting buffer
    return result.buffer
  }//sign

  /**
   * verify
   *
   * @description
   * Verifies a digital signature
   *
   * @param {CryptoKey} key
   * @param {BufferSource} signature
   * @param {BufferSource} data
   *
   * @returns {Boolean}
   */
  verify (key, signature, data) {
    let result
    // 1. Ensure the key is a public type only
    if (key.type !== 'public') {
      throw new InvalidAccessError('Verifying requires a public key')
    }
    // 2-5. Ommitted due to support by Crypto
    // 6. Attempt to verify using Crypto lib
    try {
      data = Buffer.from(data)
      signature = Buffer.from(signature)

      let verifier = crypto.createVerify('sha256')
      verifier.update(data)

      result = verifier.verify(key.handle, signature)
    } catch (error) {
      throw new OperationError(error.message)
    }
    return result
  }//verify

  /**
   * generateKey
   *
   * @description
   * Generate an ECDSA key pair
   *
   * @param {EcKeyGenParams} params
   * @returns {CryptoKeyPair}
   */
  generateKey (params, extractable, usages) {
    // 1. Validate usages
    usages.forEach(usage => {
        if (usage !== 'sign' && usage !== 'verify') {
          throw new SyntaxError('Key usages can only include "sign", or "verify"')
        }
    })

    // 2. Generate a keypair
    let keypair = {}
    let { namedCurve } = params

    if (!namedCurve) {
      throw new DataError('namedCurve is a required parameter for ECDSA')
    }

    if (!EcKeyAlgorithm.mapping.map(alg => alg.namedCurve).includes(namedCurve)) {
      throw new DataError('namedCurve is not valid')
    }

    let osslCurveName = EcKeyAlgorithm.mapping.find(alg => alg.namedCurve === namedCurve)

    try {
        // TODO may need to remove -noout if ec params is needed
        let privateKey = spawnSync('openssl', ['ecparam','-name',osslCurveName.name,'-genkey','-noout']).stdout
        let publicKey = spawnSync('openssl', ['ec', '-pubout'], { input: privateKey }).stdout
        keypair.privateKey = privateKey.toString('ascii').trim()
        keypair.publicKey = publicKey.toString('ascii').trim()
    } catch (error) {
    // 3. If any operation fails then throw error
      throw new OperationError(error.message)
    }

    // 4. Set algorithm be a new ECDSA
    let algorithm = new ECDSA(params)

    // 5-6. Set name to ECDSA
    // Defined in class header so it will be passed down via params

    // 7-11. Create publicKey object
    let publicKey = new CryptoKey({
      type: 'public',
      algorithm,
      extractable: true,
      usages: ['verify'],
      handle: keypair.publicKey
    })

    // 12-16. Create privateKey object
    let privateKey = new CryptoKey({
      type: 'private',
      algorithm,
      extractable,
      usages: ['sign'],
      handle: keypair.privateKey
    })

    // 17-20. Create and return a new CryptoKeyPair
    return new CryptoKeyPair({publicKey,privateKey})
  }//generateKey

  /**
   * importKey
   *
   * @description
   *
   * @param {string} format
   * @param {string|JsonWebKey} keyData
   * @param {KeyAlgorithm} algorithm
   * @param {Boolean} extractable
   * @param {Array} keyUsages
   *
   * @returns {CryptoKey}
   */
  importKey (format, keyData, algorithm, extractable, keyUsages) {
    let key, hash, normalizedHash, jwk, privateKeyInfo
    // 1-2. Check formatting
    // 2.1. "spki" format
    if (format === 'spki') {
      throw new CurrentlyNotSupportedError(format,'jwk')
    }

    // 2.2. "pkcs8" format
    else if (format === 'pkcs8') {
      throw new CurrentlyNotSupportedError(format,'jwk')
    }

    // 2.3. "jwk" format
    else if (format === 'jwk') {
      // 2.3.1 Ensure data is JsonWebKey dictionary
      if (typeof keyData === 'object' && !Array.isArray(keyData)){
        jwk = new JsonWebKey(keyData)
      } else {
        throw new DataError('Invalid jwk format')
      }

      // 2.3.2. Ensure 'd' field and keyUsages match up
      if (jwk.d !== undefined && keyUsages.some(usage => usage !== 'sign')) {
        throw new SyntaxError('Key usages must include "sign"')
      }

      if (jwk.d === undefined && !keyUsages.some(usage => usage === 'verify')) {
        throw new SyntaxError('Key usages must include "verify"')
      }

      // 2.3.3 Validate 'kty' field
      if (jwk.kty !== 'EC'){
        throw new DataError('Key type must be "EC".')
      }

      // 2.3.4. Validate 'use' field
      if (keyUsages !== undefined && jwk.use !== undefined && jwk.use !== 'sig'){
        throw new DataError('Key use must be "sig".')
      }

      // 2.3.5. Validate 'key_ops' field
      if (jwk.key_ops !== undefined) {
        jwk.key_ops.forEach(op => {
          if (op !== 'sign'
            && op !== 'verify' ) {
            throw new DataError('Key operation can only include "sign", or "verify".')
          }
        })
      }

      // 2.3.6. Validate 'ext' field
      if (jwk.ext !== undefined && jwk.ext === false && extractable === true){
        throw new DataError('Cannot be extractable when "ext" is set to false')
      }

      // 2.3.7. Set namedCurve
      let namedCurve = jwk.crv

      // 2.3.8. Ommitted due to redundancy

      // 2.3.9.1. If namedCurve is equal to 'secp256k1' then...
      if (EcKeyAlgorithm.mapping.map(alg => alg.namedCurve).includes(namedCurve)){
        // 2.3.9.1.1-3 Ommited due to redundancy
        // 2.3.9.1.4.1. Validate 'd' property
        if (jwk.d) {
          // 2.3.9.1.4.1.1. TODO jwk validation here...
          // 2.3.9.1.4.1.2-3 Generate new private CryptoKeyObject
          key = new CryptoKey({
              type: 'private',
              extractable,
              usages: ['sign'],
              handle: keyto.from(jwk, 'jwk').toString('pem', 'private_pkcs1')
          })
        }
        // 2.3.9.1.4.2. Otherwise...
        else {
          // 2.3.9.1.4.2.1. TODO jwk validation here...
          // 2.3.9.1.4.2.2-3 Generate new public CryptoKeyObject
          key = new CryptoKey({
            type: 'public',
            extractable: true,
            usages: ['verify'],
            handle: keyto.from(jwk, 'jwk').toString('pem', 'public_pkcs8')
          })
        }
      }
      // 2.3.9.2. Otherwise...
      else {
        // 2.3.9.2.1. TODO Implement further key import steps from other specs
        // 2.3.9.2.1. Throw error because there are currently no further specs
        throw new DataError ('Not a valid jwk specification')
      }
      // 2.3.10. Ommitted due to redudancy
      // 2.3.11-14 Set new alg object
      key.algorithm = new ECDSA(algorithm)
    }
    // 2.4. "raw" format
    else if (format === 'raw') {
      throw new CurrentlyNotSupportedError(format,'jwk')
    }
    // 2.5. Otherwise bad format
    else {
      throw new KeyFormatNotSupportedError(format)
    }
    // 3. Return key
    return key
  }//importKey

  /**
   * exportKey
   *
   * @description
   *
   * @param {string} format
   * @param {CryptoKey} key
   *
   * @returns {*}
   */
  exportKey (format, key) {
    // 1. Setup resulting var
    let result

    // 2. Validate handle slot
    if (!key.handle) {
      throw new OperationError('Missing key material')
    }

    // 3.1. "spki" format
    if (format === 'spki') {
      throw new CurrentlyNotSupportedError(format,"jwk' or 'raw")
    }
    // 3.2. "pkcs8" format
    else if (format === 'pkcs8') {
      throw new CurrentlyNotSupportedError(format,"jwk' or 'raw")
    }
    // 2.3. "jwk" format
    else if (format === 'jwk') {
      // 2.3.1-3 Create new jwk
      let jwk = keyto.from(key.handle, 'pem').toJwk(key.type)

      // 2.3.4. Set "key_ops" field
      jwk.key_ops = key.usages

      // 2.3.5. Set "ext" field
      jwk.ext = key.extractable

      // 2.3.6. Set result to jwk object
      result = jwk
    }
    // 3.4. "raw" format
    else if (format === 'raw') {
      // 3.4.1. Validate that the internal use is public
      if (key.type !== 'public'){
        throw new InvalidAccessError('Can only access public key data.')
      }
      // 3.4.2. Omitted due to redundancy
      // 3.4.3. Let resulting data be Buffer containing data
      result = Buffer.from(key.handle)
    }
    // 3.5. Otherwise bad format
    else {
      throw new KeyFormatNotSupportedError(format)
    }

    // 4. Result result
    return result
  }

}//ECDSA

/**
 * Export
 */
module.exports = ECDSA
<commit_msg>feat(ECDSA): use specified hash for different curves<commit_after>/**
 * Package dependencies
 */
const base64url = require('base64url')
const crypto = require('crypto')
const {spawnSync} = require('child_process')
const {TextEncoder, TextDecoder} = require('text-encoding')
const keyto = require('@trust/keyto')

/**
 * Local dependencies
 */
const Algorithm = require ('../algorithms/Algorithm')
const CryptoKey = require('../keys/CryptoKey')
const CryptoKeyPair = require('../keys/CryptoKeyPair')
const JsonWebKey = require('../keys/JsonWebKey')
const KeyAlgorithm = require('../dictionaries/KeyAlgorithm')
const EcKeyAlgorithm = require('../dictionaries/EcKeyAlgorithm')

/**
 * Errors
 */
const {
  DataError,
  OperationError,
  InvalidAccessError,
  KeyFormatNotSupportedError,
  CurrentlyNotSupportedError
} = require('../errors')

/**
 * ECDSA
 */
class ECDSA extends Algorithm {

  /**
   * dictionaries
   */
  static get dictionaries () {
    return [
      KeyAlgorithm,
      EcKeyAlgorithm
    ]
  }

  /**
   * members
   */
  static get members () {
    return {
      name: String,
      namedCurve: String,
      hash: 'HashAlgorithmIdentifier'
    }
  }

  /**
   * sign
   *
   * @description
   * Create a digital signature
   *
   * @param {CryptoKey} key
   * @param {BufferSource} data
   *
   * @returns {ArrayBuffer}
   */
  sign (key, data) {
    let result
    // 1. Ensure the key is a private type only
    if (key.type !== 'private') {
      throw new InvalidAccessError('Signing requires a private key')
    }
    // 2-5. Ommitted due to support by Crypto
    // 6. Attempt to sign using Crypto lib
    try {
      let osslCurveName = EcKeyAlgorithm.mapping.find(alg => alg.namedCurve === key.algorithm.name)
      data = Buffer.from(data)

      let signer = crypto.createSign(osslCurveName.hash)
      signer.update(data)

      result = signer.sign(key.handle)
    } catch (error) {
      throw new OperationError(error.message)
    }
    // 7. Return resulting buffer
    return result.buffer
  }//sign

  /**
   * verify
   *
   * @description
   * Verifies a digital signature
   *
   * @param {CryptoKey} key
   * @param {BufferSource} signature
   * @param {BufferSource} data
   *
   * @returns {Boolean}
   */
  verify (key, signature, data) {
    let result
    // 1. Ensure the key is a public type only
    if (key.type !== 'public') {
      throw new InvalidAccessError('Verifying requires a public key')
    }
    // 2-5. Ommitted due to support by Crypto
    // 6. Attempt to verify using Crypto lib
    try {
      let osslCurveName = EcKeyAlgorithm.mapping.find(alg => alg.namedCurve === key.algorithm.name)
      data = Buffer.from(data)
      signature = Buffer.from(signature)

      let verifier = crypto.createVerify(osslCurveName.hash)
      verifier.update(data)

      result = verifier.verify(key.handle, signature)
    } catch (error) {
      throw new OperationError(error.message)
    }
    return result
  }//verify

  /**
   * generateKey
   *
   * @description
   * Generate an ECDSA key pair
   *
   * @param {EcKeyGenParams} params
   * @returns {CryptoKeyPair}
   */
  generateKey (params, extractable, usages) {
    // 1. Validate usages
    usages.forEach(usage => {
        if (usage !== 'sign' && usage !== 'verify') {
          throw new SyntaxError('Key usages can only include "sign", or "verify"')
        }
    })

    // 2. Generate a keypair
    let keypair = {}
    let { namedCurve } = params

    if (!namedCurve) {
      throw new DataError('namedCurve is a required parameter for ECDSA')
    }

    if (!EcKeyAlgorithm.mapping.map(alg => alg.namedCurve).includes(namedCurve)) {
      throw new DataError('namedCurve is not valid')
    }

    let osslCurveName = EcKeyAlgorithm.mapping.find(alg => alg.namedCurve === namedCurve)

    try {
        // TODO may need to remove -noout if ec params is needed
        let privateKey = spawnSync('openssl', ['ecparam','-name',osslCurveName.name,'-genkey','-noout']).stdout
        let publicKey = spawnSync('openssl', ['ec', '-pubout'], { input: privateKey }).stdout
        keypair.privateKey = privateKey.toString('ascii').trim()
        keypair.publicKey = publicKey.toString('ascii').trim()
    } catch (error) {
    // 3. If any operation fails then throw error
      throw new OperationError(error.message)
    }

    // 4. Set algorithm be a new ECDSA
    let algorithm = new ECDSA(params)

    // 5-6. Set name to ECDSA
    // Defined in class header so it will be passed down via params

    // 7-11. Create publicKey object
    let publicKey = new CryptoKey({
      type: 'public',
      algorithm,
      extractable: true,
      usages: ['verify'],
      handle: keypair.publicKey
    })

    // 12-16. Create privateKey object
    let privateKey = new CryptoKey({
      type: 'private',
      algorithm,
      extractable,
      usages: ['sign'],
      handle: keypair.privateKey
    })

    // 17-20. Create and return a new CryptoKeyPair
    return new CryptoKeyPair({publicKey,privateKey})
  }//generateKey

  /**
   * importKey
   *
   * @description
   *
   * @param {string} format
   * @param {string|JsonWebKey} keyData
   * @param {KeyAlgorithm} algorithm
   * @param {Boolean} extractable
   * @param {Array} keyUsages
   *
   * @returns {CryptoKey}
   */
  importKey (format, keyData, algorithm, extractable, keyUsages) {
    let key, hash, normalizedHash, jwk, privateKeyInfo
    // 1-2. Check formatting
    // 2.1. "spki" format
    if (format === 'spki') {
      throw new CurrentlyNotSupportedError(format,'jwk')
    }

    // 2.2. "pkcs8" format
    else if (format === 'pkcs8') {
      throw new CurrentlyNotSupportedError(format,'jwk')
    }

    // 2.3. "jwk" format
    else if (format === 'jwk') {
      // 2.3.1 Ensure data is JsonWebKey dictionary
      if (typeof keyData === 'object' && !Array.isArray(keyData)){
        jwk = new JsonWebKey(keyData)
      } else {
        throw new DataError('Invalid jwk format')
      }

      // 2.3.2. Ensure 'd' field and keyUsages match up
      if (jwk.d !== undefined && keyUsages.some(usage => usage !== 'sign')) {
        throw new SyntaxError('Key usages must include "sign"')
      }

      if (jwk.d === undefined && !keyUsages.some(usage => usage === 'verify')) {
        throw new SyntaxError('Key usages must include "verify"')
      }

      // 2.3.3 Validate 'kty' field
      if (jwk.kty !== 'EC'){
        throw new DataError('Key type must be "EC".')
      }

      // 2.3.4. Validate 'use' field
      if (keyUsages !== undefined && jwk.use !== undefined && jwk.use !== 'sig'){
        throw new DataError('Key use must be "sig".')
      }

      // 2.3.5. Validate 'key_ops' field
      if (jwk.key_ops !== undefined) {
        jwk.key_ops.forEach(op => {
          if (op !== 'sign'
            && op !== 'verify' ) {
            throw new DataError('Key operation can only include "sign", or "verify".')
          }
        })
      }

      // 2.3.6. Validate 'ext' field
      if (jwk.ext !== undefined && jwk.ext === false && extractable === true){
        throw new DataError('Cannot be extractable when "ext" is set to false')
      }

      // 2.3.7. Set namedCurve
      let namedCurve = jwk.crv

      // 2.3.8. Ommitted due to redundancy

      // 2.3.9.1. If namedCurve is equal to 'secp256k1' then...
      if (EcKeyAlgorithm.mapping.map(alg => alg.namedCurve).includes(namedCurve)){
        // 2.3.9.1.1-3 Ommited due to redundancy
        // 2.3.9.1.4.1. Validate 'd' property
        if (jwk.d) {
          // 2.3.9.1.4.1.1. TODO jwk validation here...
          // 2.3.9.1.4.1.2-3 Generate new private CryptoKeyObject
          key = new CryptoKey({
              type: 'private',
              extractable,
              usages: ['sign'],
              handle: keyto.from(jwk, 'jwk').toString('pem', 'private_pkcs1')
          })
        }
        // 2.3.9.1.4.2. Otherwise...
        else {
          // 2.3.9.1.4.2.1. TODO jwk validation here...
          // 2.3.9.1.4.2.2-3 Generate new public CryptoKeyObject
          key = new CryptoKey({
            type: 'public',
            extractable: true,
            usages: ['verify'],
            handle: keyto.from(jwk, 'jwk').toString('pem', 'public_pkcs8')
          })
        }
      }
      // 2.3.9.2. Otherwise...
      else {
        // 2.3.9.2.1. TODO Implement further key import steps from other specs
        // 2.3.9.2.1. Throw error because there are currently no further specs
        throw new DataError ('Not a valid jwk specification')
      }
      // 2.3.10. Ommitted due to redudancy
      // 2.3.11-14 Set new alg object
      key.algorithm = new ECDSA(algorithm)
    }
    // 2.4. "raw" format
    else if (format === 'raw') {
      throw new CurrentlyNotSupportedError(format,'jwk')
    }
    // 2.5. Otherwise bad format
    else {
      throw new KeyFormatNotSupportedError(format)
    }
    // 3. Return key
    return key
  }//importKey

  /**
   * exportKey
   *
   * @description
   *
   * @param {string} format
   * @param {CryptoKey} key
   *
   * @returns {*}
   */
  exportKey (format, key) {
    // 1. Setup resulting var
    let result

    // 2. Validate handle slot
    if (!key.handle) {
      throw new OperationError('Missing key material')
    }

    // 3.1. "spki" format
    if (format === 'spki') {
      throw new CurrentlyNotSupportedError(format,"jwk' or 'raw")
    }
    // 3.2. "pkcs8" format
    else if (format === 'pkcs8') {
      throw new CurrentlyNotSupportedError(format,"jwk' or 'raw")
    }
    // 2.3. "jwk" format
    else if (format === 'jwk') {
      // 2.3.1-3 Create new jwk
      let jwk = keyto.from(key.handle, 'pem').toJwk(key.type)

      // 2.3.4. Set "key_ops" field
      jwk.key_ops = key.usages

      // 2.3.5. Set "ext" field
      jwk.ext = key.extractable

      // 2.3.6. Set result to jwk object
      result = jwk
    }
    // 3.4. "raw" format
    else if (format === 'raw') {
      // 3.4.1. Validate that the internal use is public
      if (key.type !== 'public'){
        throw new InvalidAccessError('Can only access public key data.')
      }
      // 3.4.2. Omitted due to redundancy
      // 3.4.3. Let resulting data be Buffer containing data
      result = Buffer.from(key.handle)
    }
    // 3.5. Otherwise bad format
    else {
      throw new KeyFormatNotSupportedError(format)
    }

    // 4. Result result
    return result
  }

}//ECDSA

/**
 * Export
 */
module.exports = ECDSA
<|endoftext|>


<commit_before>//
// HLF Hover Intent Extension
// ==========================
// [Tests](../../tests/js/hover-intent.html)
//
// The `hoverIntent` extension normalizes DOM events associated with mouse enter
// and leave interaction. It prevents the 'thrashing' of attached behaviors (ex:
// non-cancel-able animations) when matching mouse input arrives at frequencies
// past the threshold. It is heavily inspired by Brian Cherne's jQuery plugin of
// the same name (github.com/briancherne/jquery-hoverIntent).
(function(root, attach) {
  //
  // § __UMD__
  // - When AMD, register the attacher as an anonymous module.
  // - When Node or Browserify, set module exports to the attach result.
  // - When browser globals (root is window), Just run the attach function.
  //
  if (typeof define === 'function' && define.amd) {
    define(['hlf/core'], attach);
  } else if (typeof exports === 'object') {
    module.exports = attach(require('hlf/core'));
  } else {
    attach(hlf);
  }
})(this, function(hlf) {
  //
  // Namespace
  // ---------
  //
  // It takes some more boilerplate to write the extension. Any of this additional
  // support API is put into a extension specific namespace under `hlf`, which in
  // this case is __hlf.hoverIntent__.
  //
  // - __debug__ toggles debug logging for all instances of an extension.
  // - __toString__ helps to namespace when registering any DOM names.
  // - __attrName__, __className__, __eventName__ helpers are all attached to
  //   the namespace on extension creation, along with the __extension__ itself.
  //
  // The extension's __defaults__ are available as reference. Also note that _the
  // extension instance gets extended with the options_.
  //
  // - __interval__ is the millis to wait before deciding intent. `300` is the
  //   default to reduce more thrashing.
  //
  // - __sensitivity__ is the pixel threshold for mouse travel between polling
  //   intervals. With the minimum sensitivity threshold of 1, the mouse must
  //   not move between intervals. With higher values yield more false positives.
  //   `8` is the default to allow more flexibility.
  //
  // The events dispatched are the name-spaced `enter` and `leave` events. They
  // try to match system mouse events where possible and include values for:
  // `pageX`, `pageY`, `relatedTarget`.
  //
  hlf.hoverIntent = {
    debug: true,
    defaults: {
      interval: 300,
      sensitivity: 8,
    },
    toString(context) {
      switch (context) {
        case 'event': return 'hlfhi';
        case 'data': return 'hlf-hi';
        default: return 'hlf-hi';
      }
    },
  };
  //
  // HoverIntent
  // -----------
  //
  // To summarize the implementation, the `_onMouseOver` handler is the start of
  // the intent 'life-cycle', with state being the default. It records the mouse
  // coordinates, sets up a delayed intent check, and guards against unneeded
  // checking. Meanwhile, about every frame, the `_onMouseMove` handler tracks
  // the current mouse coordinates. If the `_onMouseLeave` handler runs before
  // the delayed check, the check and subsequent behavior get canceled as state
  // resets to default. Otherwise, if the check of the stored mouse coordinates
  // against `sensitivity` passes, an `enter` event gets dispatched, ensuring
  // a `leave` event will too during `_onMouseLeave`.
  //
  class HoverIntent {
    constructor(element, options) {
      this.eventListeners = {
        'mouseleave': this._onMouseLeave,
        'mousemove': this._onMouseMove,
        'mouseover': this._onMouseOver,
      };
    }
    init() {
      this._setDefaultState();
    }
    deinit() {
      this._setDefaultState();
    }
    //
    // § __Internal__
    //
    _dispatchHoverEvent(on, mouseEvent) {
      const { mouse: { x, y } } = this;
      let type = on ? 'enter' : 'leave';
      this.dispatchCustomEvent(type, {
        pageX: x.current,
        pageY: y.current,
        relatedTarget: mouseEvent.relatedTarget,
      });
      this.debugLog(type, x.current, y.current, Date.now() % 100000);
    }
    _onMouseLeave(event) {
      if (this.intentional) {
        this.debugLog('teardown');
        this._dispatchHoverEvent(false, event);
      }
      this._setDefaultState();
    }
    _onMouseMove(event) {
      if (this.trackTimeout != null) { return; }
      this.trackTimeout = setTimeout(() => {
        this.debugLog('track');
        let { mouse: { x, y } } = this;
        x.current = event.pageX;
        y.current = event.pageY;
        this.trackTimeout = null;
      }, 16);
    }
    _onMouseOver(event) {
      if (this.intentional) { return; }
      if (event.target !== this.element) { return; }
      if (this.timeout != null) { return; }
      this.debugLog('setup');
      let { mouse: { x, y } } = this;
      x.previous = event.pageX;
      y.previous = event.pageY;
      this.timeout = setTimeout(() => {
        this._setState(event);
        if (this.intentional) {
          this._dispatchHoverEvent(true, event);
        }
      }, this.interval);
    }
    _setDefaultState() {
      this.debugLog('reset');
      this.intentional = false;
      this.mouse = {
        x: { current: 0, previous: 0 },
        y: { current: 0, previous: 0 },
      };
      clearTimeout(this.timeout);
      clearTimeout(this.trackTimeout);
      this.timeout = null;
      this.trackTimeout = null;
    }
    _setState(event) {
      this.debugLog('update');
      let { mouse: { x, y } } = this;
      this.intentional = (
        Math.abs(x.previous - x.current) + Math.abs(y.previous - y.current) >
        this.sensitivity
      );
      this.timeout = null;
    }
  }
  //
  // § __Attaching__
  //
  return hlf.createExtension({
    name: 'hoverIntent',
    namespace: hlf.hoverIntent,
    apiClass: HoverIntent,
    autoBind: true,
    autoListen: true,
    baseMethodGroups: ['event'],
    compactOptions: true,
  });
});
<commit_msg>Use Euclidean distance for hover-intent checking.<commit_after>//
// HLF Hover Intent Extension
// ==========================
// [Tests](../../tests/js/hover-intent.html)
//
// The `hoverIntent` extension normalizes DOM events associated with mouse enter
// and leave interaction. It prevents the 'thrashing' of attached behaviors (ex:
// non-cancel-able animations) when matching mouse input arrives at frequencies
// past the threshold. It is heavily inspired by Brian Cherne's jQuery plugin of
// the same name (github.com/briancherne/jquery-hoverIntent).
(function(root, attach) {
  //
  // § __UMD__
  // - When AMD, register the attacher as an anonymous module.
  // - When Node or Browserify, set module exports to the attach result.
  // - When browser globals (root is window), Just run the attach function.
  //
  if (typeof define === 'function' && define.amd) {
    define(['hlf/core'], attach);
  } else if (typeof exports === 'object') {
    module.exports = attach(require('hlf/core'));
  } else {
    attach(hlf);
  }
})(this, function(hlf) {
  //
  // Namespace
  // ---------
  //
  // It takes some more boilerplate to write the extension. Any of this additional
  // support API is put into a extension specific namespace under `hlf`, which in
  // this case is __hlf.hoverIntent__.
  //
  // - __debug__ toggles debug logging for all instances of an extension.
  // - __toString__ helps to namespace when registering any DOM names.
  // - __attrName__, __className__, __eventName__ helpers are all attached to
  //   the namespace on extension creation, along with the __extension__ itself.
  //
  // The extension's __defaults__ are available as reference. Also note that _the
  // extension instance gets extended with the options_.
  //
  // - __interval__ is the millis to wait before deciding intent. `300` is the
  //   default to reduce more thrashing.
  //
  // - __sensitivity__ is the pixel threshold for mouse travel between polling
  //   intervals. With the minimum sensitivity threshold of 1, the mouse must
  //   not move between intervals. With higher values yield more false positives.
  //   `6` is the default.
  //
  // The events dispatched are the name-spaced `enter` and `leave` events. They
  // try to match system mouse events where possible and include values for:
  // `pageX`, `pageY`, `relatedTarget`.
  //
  hlf.hoverIntent = {
    debug: true,
    defaults: {
      interval: 300,
      sensitivity: 6,
    },
    toString(context) {
      switch (context) {
        case 'event': return 'hlfhi';
        case 'data': return 'hlf-hi';
        default: return 'hlf-hi';
      }
    },
  };
  //
  // HoverIntent
  // -----------
  //
  // To summarize the implementation, the `_onMouseOver` handler is the start of
  // the intent 'life-cycle', with state being the default. It records the mouse
  // coordinates, sets up a delayed intent check, and guards against unneeded
  // checking. Meanwhile, about every frame, the `_onMouseMove` handler tracks
  // the current mouse coordinates. If the `_onMouseLeave` handler runs before
  // the delayed check, the check and subsequent behavior get canceled as state
  // resets to default. Otherwise, if the check of the stored mouse coordinates
  // against `sensitivity` passes, an `enter` event gets dispatched, ensuring
  // a `leave` event will too during `_onMouseLeave`.
  //
  class HoverIntent {
    constructor(element, options) {
      this.eventListeners = {
        'mouseleave': this._onMouseLeave,
        'mousemove': this._onMouseMove,
        'mouseover': this._onMouseOver,
      };
    }
    init() {
      this._setDefaultState();
    }
    deinit() {
      this._setDefaultState();
    }
    //
    // § __Internal__
    //
    _dispatchHoverEvent(on, mouseEvent) {
      const { mouse: { x, y } } = this;
      let type = on ? 'enter' : 'leave';
      this.dispatchCustomEvent(type, {
        pageX: x.current,
        pageY: y.current,
        relatedTarget: mouseEvent.relatedTarget,
      });
      this.debugLog(type, x.current, y.current, Date.now() % 100000);
    }
    _onMouseLeave(event) {
      if (this.intentional) {
        this.debugLog('teardown');
        this._dispatchHoverEvent(false, event);
      }
      this._setDefaultState();
    }
    _onMouseMove(event) {
      if (this.trackTimeout != null) { return; }
      this.trackTimeout = setTimeout(() => {
        this.debugLog('track');
        let { mouse: { x, y } } = this;
        x.current = event.pageX;
        y.current = event.pageY;
        this.trackTimeout = null;
      }, 16);
    }
    _onMouseOver(event) {
      if (this.intentional) { return; }
      if (event.target !== this.element) { return; }
      if (this.timeout != null) { return; }
      this.debugLog('setup');
      let { mouse: { x, y } } = this;
      x.previous = event.pageX;
      y.previous = event.pageY;
      this.timeout = setTimeout(() => {
        this._setState(event);
        if (this.intentional) {
          this._dispatchHoverEvent(true, event);
        }
      }, this.interval);
    }
    _setDefaultState() {
      this.debugLog('reset');
      this.intentional = false;
      this.mouse = {
        x: { current: 0, previous: 0 },
        y: { current: 0, previous: 0 },
      };
      clearTimeout(this.timeout);
      clearTimeout(this.trackTimeout);
      this.timeout = null;
      this.trackTimeout = null;
    }
    _setState(event) {
      this.debugLog('update');
      let { mouse: { x, y } } = this;
      const { abs, pow, sqrt } = Math;
      this.intentional = (
        abs(
          sqrt(pow(x.previous, 2) + pow(y.previous, 2)) -
          sqrt(pow(x.current, 2) + pow(y.current, 2))
        ) > this.sensitivity
      );
      this.timeout = null;
    }
  }
  //
  // § __Attaching__
  //
  return hlf.createExtension({
    name: 'hoverIntent',
    namespace: hlf.hoverIntent,
    apiClass: HoverIntent,
    autoBind: true,
    autoListen: true,
    baseMethodGroups: ['event'],
    compactOptions: true,
  });
});
<|endoftext|>


<commit_before>angular.module('ngCountTo', [])
    .directive('ngCountTo', ['$filter', '$timeout', function ($filter, $timeout) {
        return {
            replace: false,
            scope: true,
            link: function (scope, element, attrs) {

                var e = element[0];
                var num, refreshInterval, duration, steps, step, ngCountTo, value, increment;

                var calculate = function () {
                    refreshInterval = 30;
                    step = 0;
                    scope.timoutId = null;
                    scope.filter = attrs.filter;
                    scope.fractionSize = attrs.fractionSize ? attrs.fractionSize : 0;
                    scope.params = attrs.params ? attrs.params : scope.fractionSize;
                    ngCountTo = parseFloat(attrs.ngCountTo) || 0;
                    scope.value = parseFloat(attrs.value, 10) || 0;
                    duration = (parseFloat(attrs.duration) * 1000) || 0;

                    steps = Math.ceil(duration / refreshInterval);
                    increment = ((ngCountTo - scope.value) / steps);
                    num = scope.value;
                }

                var tick = function () {
                    scope.timoutId = $timeout(function () {
                        num += increment;
                        step++;
                        if (step >= steps) {
                            $timeout.cancel(scope.timoutId);
                            num = ngCountTo;
                            e.textContent = scope.filter ? $filter(scope.filter)(ngCountTo, scope.params, scope.fractionSize) : Math.round(countTo);
                        } else {
                            e.textContent = scope.filter ?  $filter(scope.filter)(num, scope.params, scope.fractionSize) : Math.round(num);
                            tick();
                        }
                    }, refreshInterval);

                }

                var start = function () {
                    if (scope.timoutId) {
                        $timeout.cancel(scope.timoutId);
                    }
                    calculate();
                    tick();
                }

                attrs.$observe('ngCountTo', function (val) {
                    if (val) {
                        start();
                    }
                });

                attrs.$observe('value', function (val) {
                    start();
                });

                return true;
            }
        }

    }]);
<commit_msg>Fix undefined variable countTo<commit_after>angular.module('ngCountTo', [])
    .directive('ngCountTo', ['$filter', '$timeout', function ($filter, $timeout) {
        return {
            replace: false,
            scope: true,
            link: function (scope, element, attrs) {

                var e = element[0];
                var num, refreshInterval, duration, steps, step, ngCountTo, value, increment;

                var calculate = function () {
                    refreshInterval = 30;
                    step = 0;
                    scope.timoutId = null;
                    scope.filter = attrs.filter;
                    scope.fractionSize = attrs.fractionSize ? attrs.fractionSize : 0;
                    scope.params = attrs.params ? attrs.params : scope.fractionSize;
                    ngCountTo = parseFloat(attrs.ngCountTo) || 0;
                    scope.value = parseFloat(attrs.value, 10) || 0;
                    duration = (parseFloat(attrs.duration) * 1000) || 0;

                    steps = Math.ceil(duration / refreshInterval);
                    increment = ((ngCountTo - scope.value) / steps);
                    num = scope.value;
                }

                var tick = function () {
                    scope.timoutId = $timeout(function () {
                        num += increment;
                        step++;
                        if (step >= steps) {
                            $timeout.cancel(scope.timoutId);
                            num = ngCountTo;
                            e.textContent = scope.filter ? $filter(scope.filter)(ngCountTo, scope.params, scope.fractionSize) : Math.round(ngCountTo);
                        } else {
                            e.textContent = scope.filter ?  $filter(scope.filter)(num, scope.params, scope.fractionSize) : Math.round(num);
                            tick();
                        }
                    }, refreshInterval);

                }

                var start = function () {
                    if (scope.timoutId) {
                        $timeout.cancel(scope.timoutId);
                    }
                    calculate();
                    tick();
                }

                attrs.$observe('ngCountTo', function (val) {
                    if (val) {
                        start();
                    }
                });

                attrs.$observe('value', function (val) {
                    start();
                });

                return true;
            }
        }

    }]);
<|endoftext|>


<commit_before>
var inside = require('point-in-polygon');
var Polygon = require('polygon');
var Vec2 = require('vec2');
var _ = require('underscore');
var countries = require('i18n-iso-countries');
var d3 = require('d3');

var MapModel = function(featureData) {
  this.featureData = featureData;

  this._countryFeatureCache = {};
  this._countryBordersCache = {};
  this._countryCentersCache = {};
  this._countryBoundsCache = {};
  this._labelFeatureCache = {};

  this.initialize();
};

MapModel.prototype.initialize = function() {
  // the centroid isn't always good. fix for these countries:
  this._countryCentersCache['FRA'] = [2.449486512892406, 46.62237366531258];
  this._countryCentersCache['SWE'] = [14.273817, 59.803497];
  this._countryCentersCache['FIN'] = [25.356445, 62.890593];
  this._countryCentersCache['NOR'] = [8.506239, 60.975869];
  this._countryCentersCache['GBR'] = [-1.538086, 52.815213];
  this._countryCentersCache['GRC'] = [21.752930, 39.270271];
  this._countryCentersCache['RUS'] = [51.328125, 56.641127];
  this._countryCentersCache['HUN'] = [18.632813, 47.159840];
};

MapModel.prototype.containsCountry = function(country) {
  return this.getFeatureForCountry(country) != null;
};

MapModel.prototype.getFeatureForCountry = function(country) {
  if (this._countryFeatureCache[country]) return this._countryFeatureCache[country];

  var countryFeature = _.find(
    this.featureData.features,
    function(f) { return f.properties.ADM0_A3 == country; });

  if (countryFeature) {
    this._countryFeatureCache[country] = countryFeature;
    return countryFeature;
  }
  return null;
};

MapModel.prototype.getLabelPointForCountry = function(country) {

  var feature = this.getLabelFeatureForCountry(country);

  if (feature) {
    return feature.geometry.coordinates;
  }

  console.log('could not find label point for ' + country); // eslint-disable-line
  return [0, 0];
};

// Currently only support fi and en. Falls back to en
MapModel.prototype.getFriendlyNameForCountry = function(country, locale) {
  switch (locale) {
    case 'fi':
      switch(country) {
        case 'SYR': return 'Syyria';
        case 'MKD': return 'Makedonia';
        case 'IRN': return 'Iran';
        case 'LBY': return 'Libya';
        case 'RUS': return 'Venäjä';
        case 'RCB': return 'Kongo';
        case 'COD': return 'Kongo';
        case 'GBR': return 'Iso-Britannia';
        case 'KOS': return 'Kosovo';
        default: return countries.getName(country, 'fi');
      }
    case 'en':
    default:
      switch(country) {
        case 'MDA': return 'Moldova';
        case 'SYR': return 'Syria';
        case 'MKD': return 'Macedonia';
        case 'IRN': return 'Iran';
        case 'LBY': return 'Libya';
        case 'RUS': return 'Russia';
        case 'RCB': return 'Congo';
        case 'COD': return 'Congo';
        case 'KOS': return 'Kosovo';
        default: return countries.getName(country, 'en');
      }
  }
};

MapModel.prototype.getRandomPointFromCountry = function(country) {
  var feature = this.getFeatureForCountry(country);
  if (feature == null) {
    throw 'could not find feature for ' + country;
  }
  var borders = this.getMainCountryBorderForFeature(feature);
  return this.getRandomPointForCountryBorder(country, borders);
};

MapModel.prototype.getMainCountryBorderForFeature = function(feature) {
  var key = feature.properties.ADM0_A3;
  if (this._countryBordersCache[key] == null) {
    if (feature.geometry.type == 'MultiPolygon') {
      this._countryBordersCache[key] = MapModel.getLargestPolygon(feature.geometry.coordinates);
    } else {
      this._countryBordersCache[key] = feature.geometry.coordinates[0];
    }
  }
  return this._countryBordersCache[key];
};

MapModel.prototype.getCenterPointOfCountry = function(country) {
  if (!this._countryCentersCache[country]) {
    var feature = this.getFeatureForCountry(country);
    if (feature == null) {
      //console.log('could not find feature for ' + country);
      return [0, 0];
    }
    this._countryCentersCache[country] = d3.geo.centroid(feature);
  }
  return this._countryCentersCache[country];
};

/*
 * Get a random point within the polygon defined
 * by the coordinates in the given array
 */
MapModel.prototype.getRandomPointForCountryBorder = function(country, coordinates) {
  if (!this._countryBoundsCache[country]) {
    this._countryBoundsCache[country] = MapModel.getBounds(coordinates);
  }
  var bounds = this._countryBoundsCache[country];
  var la, lo;

  var count = 0;
  do {
    la = Math.random() * (bounds.maxLa - bounds.minLa) + bounds.minLa;
    lo = Math.random() * (bounds.maxLo - bounds.minLo) + bounds.minLo;
    count++;
  } while (!inside([la, lo], coordinates) && count < 100);

  if (count == 100) {
    console.log('could not create random point for ' + country); // eslint-disable-line
    return [0, 0];
  }
  return [la, lo];
};

/*
 * Get largest polygon within a GeoJSON
 * MultiPolygon coordinates array
 *
 * (used to figure out what is mainland)
 */
MapModel.getLargestPolygon = function(coordinates) {
  var largest = Number.MIN_VALUE;
  var ret = null;
  coordinates.forEach(function(item) {
    var va = item[0].map(function(point) {
      return Vec2(point[0], point[1]);
    });

    var p = new Polygon(va);
    var val = p.area();
    if (val > largest) {
      largest = val;
      ret = item[0];
    }
  });
  return ret;
};

MapModel.getBounds = function(coordinates) {
  var bounds = coordinates.reduce(function(previous, item) {
    previous.minLa = Math.min(item[0], previous.minLa);
    previous.maxLa = Math.max(item[0], previous.maxLa);
    previous.minLo = Math.min(item[1], previous.minLo);
    previous.maxLo = Math.max(item[1], previous.maxLo);

    return previous;

  }, {minLa: Number.MAX_VALUE,
    maxLa: Number.MIN_VALUE,
    minLo: Number.MAX_VALUE,
    maxLo: Number.MIN_VALUE});
  return bounds;
};

module.exports = MapModel;
<commit_msg>Move label/bar chart for Germany<commit_after>
var inside = require('point-in-polygon');
var Polygon = require('polygon');
var Vec2 = require('vec2');
var _ = require('underscore');
var countries = require('i18n-iso-countries');
var d3 = require('d3');

var MapModel = function(featureData) {
  this.featureData = featureData;

  this._countryFeatureCache = {};
  this._countryBordersCache = {};
  this._countryCentersCache = {};
  this._countryBoundsCache = {};
  this._labelFeatureCache = {};

  this.initialize();
};

MapModel.prototype.initialize = function() {
  // the centroid isn't always good. fix for these countries:
  this._countryCentersCache['FRA'] = [2.449486, 46.622373];
  this._countryCentersCache['SWE'] = [14.273817, 59.803497];
  this._countryCentersCache['FIN'] = [25.356445, 62.890593];
  this._countryCentersCache['NOR'] = [8.506239, 60.975869];
  this._countryCentersCache['DEU'] = [9.703750, 50.998200];
  this._countryCentersCache['GBR'] = [-1.538086, 52.815213];
  this._countryCentersCache['GRC'] = [21.752930, 39.270271];
  this._countryCentersCache['RUS'] = [51.328125, 56.641127];
  this._countryCentersCache['HUN'] = [18.632813, 47.159840];
};

MapModel.prototype.containsCountry = function(country) {
  return this.getFeatureForCountry(country) != null;
};

MapModel.prototype.getFeatureForCountry = function(country) {
  if (this._countryFeatureCache[country]) return this._countryFeatureCache[country];

  var countryFeature = _.find(
    this.featureData.features,
    function(f) { return f.properties.ADM0_A3 == country; });

  if (countryFeature) {
    this._countryFeatureCache[country] = countryFeature;
    return countryFeature;
  }
  return null;
};

MapModel.prototype.getLabelPointForCountry = function(country) {

  var feature = this.getLabelFeatureForCountry(country);

  if (feature) {
    return feature.geometry.coordinates;
  }

  console.log('could not find label point for ' + country); // eslint-disable-line
  return [0, 0];
};

// Currently only support fi and en. Falls back to en
MapModel.prototype.getFriendlyNameForCountry = function(country, locale) {
  switch (locale) {
    case 'fi':
      switch(country) {
        case 'SYR': return 'Syyria';
        case 'MKD': return 'Makedonia';
        case 'IRN': return 'Iran';
        case 'LBY': return 'Libya';
        case 'RUS': return 'Venäjä';
        case 'RCB': return 'Kongo';
        case 'COD': return 'Kongo';
        case 'GBR': return 'Iso-Britannia';
        case 'KOS': return 'Kosovo';
        default: return countries.getName(country, 'fi');
      }
    case 'en':
    default:
      switch(country) {
        case 'MDA': return 'Moldova';
        case 'SYR': return 'Syria';
        case 'MKD': return 'Macedonia';
        case 'IRN': return 'Iran';
        case 'LBY': return 'Libya';
        case 'RUS': return 'Russia';
        case 'RCB': return 'Congo';
        case 'COD': return 'Congo';
        case 'KOS': return 'Kosovo';
        default: return countries.getName(country, 'en');
      }
  }
};

MapModel.prototype.getRandomPointFromCountry = function(country) {
  var feature = this.getFeatureForCountry(country);
  if (feature == null) {
    throw 'could not find feature for ' + country;
  }
  var borders = this.getMainCountryBorderForFeature(feature);
  return this.getRandomPointForCountryBorder(country, borders);
};

MapModel.prototype.getMainCountryBorderForFeature = function(feature) {
  var key = feature.properties.ADM0_A3;
  if (this._countryBordersCache[key] == null) {
    if (feature.geometry.type == 'MultiPolygon') {
      this._countryBordersCache[key] = MapModel.getLargestPolygon(feature.geometry.coordinates);
    } else {
      this._countryBordersCache[key] = feature.geometry.coordinates[0];
    }
  }
  return this._countryBordersCache[key];
};

MapModel.prototype.getCenterPointOfCountry = function(country) {
  if (!this._countryCentersCache[country]) {
    var feature = this.getFeatureForCountry(country);
    if (feature == null) {
      //console.log('could not find feature for ' + country);
      return [0, 0];
    }
    this._countryCentersCache[country] = d3.geo.centroid(feature);
  }
  return this._countryCentersCache[country];
};

/*
 * Get a random point within the polygon defined
 * by the coordinates in the given array
 */
MapModel.prototype.getRandomPointForCountryBorder = function(country, coordinates) {
  if (!this._countryBoundsCache[country]) {
    this._countryBoundsCache[country] = MapModel.getBounds(coordinates);
  }
  var bounds = this._countryBoundsCache[country];
  var la, lo;

  var count = 0;
  do {
    la = Math.random() * (bounds.maxLa - bounds.minLa) + bounds.minLa;
    lo = Math.random() * (bounds.maxLo - bounds.minLo) + bounds.minLo;
    count++;
  } while (!inside([la, lo], coordinates) && count < 100);

  if (count == 100) {
    console.log('could not create random point for ' + country); // eslint-disable-line
    return [0, 0];
  }
  return [la, lo];
};

/*
 * Get largest polygon within a GeoJSON
 * MultiPolygon coordinates array
 *
 * (used to figure out what is mainland)
 */
MapModel.getLargestPolygon = function(coordinates) {
  var largest = Number.MIN_VALUE;
  var ret = null;
  coordinates.forEach(function(item) {
    var va = item[0].map(function(point) {
      return Vec2(point[0], point[1]);
    });

    var p = new Polygon(va);
    var val = p.area();
    if (val > largest) {
      largest = val;
      ret = item[0];
    }
  });
  return ret;
};

MapModel.getBounds = function(coordinates) {
  var bounds = coordinates.reduce(function(previous, item) {
    previous.minLa = Math.min(item[0], previous.minLa);
    previous.maxLa = Math.max(item[0], previous.maxLa);
    previous.minLo = Math.min(item[1], previous.minLo);
    previous.maxLo = Math.max(item[1], previous.maxLo);

    return previous;

  }, {minLa: Number.MAX_VALUE,
    maxLa: Number.MIN_VALUE,
    minLo: Number.MAX_VALUE,
    maxLo: Number.MIN_VALUE});
  return bounds;
};

module.exports = MapModel;
<|endoftext|>