<commit_before>/*global*/
emp.editors = emp.editors || {};

emp.editors.Ellipse = function(args) {
  this.animation = undefined;
  this.semiMinor = undefined;
  this.semiMajor = undefined;
  this.center = undefined;
  emp.editors.EditorBase.call(this, args);
};

emp.editors.Ellipse.prototype = Object.create(emp.editors.EditorBase.prototype);
emp.editors.Ellipse.prototype.constructor = emp.editors.Ellipse;

/**
 * Adds the control points to the map for an edit or a draw.
 */
emp.editors.Ellipse.prototype.addControlPoints = function() {

  var controlPoint,
    transaction,
    items = [],
    vertex,
    semiMajor,
    semiMinor,
    semiMinorPoint,
    semiMajorPoint,
    azimuthPoint,
    semiMajorFeature,
    semiMinorFeature,
    azimuthFeature,
    semiMajorVertex,
    semiMinorVertex,
    azimuthVertex,
    x, y;

  x = this.featureCopy.data.coordinates[0];
  y = this.featureCopy.data.coordinates[1];

  // Create a feature on the map for the center of the ellipse.
  controlPoint = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    data: {
      coordinates: [x, y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.editPoint,
      iconXOffset: 12,
      iconYOffset: 12,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  // Create a vertex.  This is so the editingManager knows that
  // the center point is a control point .
  vertex = new emp.editors.Vertex(controlPoint, "vertex");
  this.vertices.push(vertex);
  this.center = vertex;
  items.push(controlPoint);

  // Creates control points.
  semiMinor = this.featureCopy.properties.semiMinor;
  semiMajor = this.featureCopy.properties.semiMajor;

  semiMinorPoint = emp.geoLibrary.geodesic_coordinate({
    x: x,
    y: y
  }, semiMinor, 0);
  semiMajorPoint = emp.geoLibrary.geodesic_coordinate({
    x: x,
    y: y
  }, semiMajor, 90);
  azimuthPoint = emp.geoLibrary.geodesic_coordinate({
    x: x,
    y: y
  }, semiMajor, -90);

  // create a feature for each of these coordinates.

  semiMinorFeature = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    name: "semiMinor",
    data: {
      coordinates: [semiMinorPoint.x, semiMinorPoint.y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.addPoint,
      iconXOffset: 8,
      iconYOffset: 8,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  semiMajorFeature = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    name: "semiMajor",
    data: {
      coordinates: [semiMajorPoint.x, semiMajorPoint.y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.addPoint,
      iconXOffset: 8,
      iconYOffset: 8,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  azimuthFeature = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    name: "azimuth",
    data: {
      coordinates: [azimuthPoint.x, azimuthPoint.y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.rotationPoint,
      iconXOffset: 12,
      iconYOffset: 12,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  items.push(semiMinorFeature);
  items.push(semiMajorFeature);
  items.push(azimuthFeature);

  semiMajorVertex = new emp.editors.Vertex(semiMajorFeature, "add");
  semiMinorVertex = new emp.editors.Vertex(semiMinorFeature, "add");
  azimuthVertex = new emp.editors.Vertex(azimuthFeature, "add");

  this.semiMajor = semiMajorVertex;
  this.semiMinor = semiMinorVertex;
  this.azimuth = azimuthVertex;

  this.vertices.push(semiMajorVertex);
  this.vertices.push(semiMinorVertex);
  this.vertices.push(azimuthVertex);

  // run the transaction and add all the symbols on the map.
  transaction = new emp.typeLibrary.Transaction({
    intent: emp.intents.control.FEATURE_ADD,
    mapInstanceId: this.mapInstance.mapInstanceId,
    transactionId: null,
    sender: this.mapInstance.mapInstanceId,
    originChannel: cmapi.channel.names.MAP_FEATURE_PLOT,
    source: emp.api.cmapi.SOURCE,
    messageOriginator: this.mapInstance.mapInstanceId,
    originalMessageType: cmapi.channel.names.MAP_FEATURE_PLOT,
    items: items
  });
  transaction.run();

};

/**
 * Begins the movement of a control point.
 */
emp.editors.Ellipse.prototype.startMoveControlPoint = function(featureId, pointer) {

  var currentFeature,
    currentVertex,
    azimuth,
    pointAzimuth,
    items = [],
    semiMajor,
    semiMinor,
    azimuth,
    newSemiMajorPosition,
    newSemiMinorPosition,
    alternateVertex,
    alternateFeature,
    index,
    coordinateUpdate,
    updateData = {},
    type = emp.typeLibrary.CoordinateUpdateType.UPDATE,
    newCoordinates,
    x, y;

  x = this.featureCopy.data.coordinates[0];
  y = this.featureCopy.data.coordinates[1];

  currentVertex = this.vertices.find(featureId);
  currentFeature = currentVertex.feature;

  azimuth = this.featureCopy.properties.azimuth;

  // Calculate the new position of where
  // we want the control point to be.
  if (featureId === this.semiMajor.feature.featureId){
    pointAzimuth = 90;
    //set the alternate so we can update that too
    alternateVertex = this.vertices.find(this.semiMinor.feature.featureId);
    alternateFeature = alternateVertex.feature;

    // measure the distance between the mouse location and the center.  This
    // will be the new semiMajor.
    semiMajor = emp.geoLibrary.measureDistance(pointer.lat,
      pointer.lon,
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

    //ensure that semiMajor is always greater than semiMinor
    if(semiMajor < this.featureCopy.properties.semiMinor){
      semiMinor = semiMajor;
      semiMajor = this.featureCopy.properties.semiMinor;
      this.featureCopy.properties.semiMinor = semiMinor;
      pointAzimuth = 0;

      // retrieve the new semiMajor vertex.
      newSemiMinorPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMinor, 90);

      //update the alternate feature
      alternateFeature.data.coordinates = [newSemiMinorPosition.x, newSemiMinorPosition.y];
    }else{
      alternateFeature = null;
    }

    // retrieve the new semiMajor vertex.
    newSemiMajorPosition = emp.geoLibrary.geodesic_coordinate({
      x: x,
      y: y
    }, semiMajor, pointAzimuth);

    if(alternateFeature){
      newAzimuthPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMinor, -90);
    }else{
      newAzimuthPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMajor, -90);
    }
    // First update the control point with new pointer info.
    currentFeature.data.coordinates = [newSemiMajorPosition.x, newSemiMajorPosition.y];
    this.azimuth.feature.data.coordinates = [newAzimuthPosition.x, newAzimuthPosition.y];
    this.featureCopy.properties.semiMajor = semiMajor;
    items.push(this.azimuth.feature);

  }else if (featureId === this.semiMinor.feature.featureId){
    pointAzimuth = 0;
    //set the alternate so we can update that too
    alternateVertex = this.vertices.find(this.semiMajor.feature.featureId);
    alternateFeature = alternateVertex.feature;

    // measure the distance between the mouse location and the center.  This
    // will be the new semiMinor.
    semiMinor = emp.geoLibrary.measureDistance(pointer.lat,
      pointer.lon,
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

        //ensure that semiMajor is always greater than semiMinor
    if(semiMinor > this.featureCopy.properties.semiMajor){
      semiMajor = semiMinor;
      semiMinor = this.featureCopy.properties.semiMajor;
      this.featureCopy.properties.semiMajor = semiMajor;
      pointAzimuth = 90;

      // retrieve the new semiMajor vertex.
      newSemiMajorPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMajor);

      //update the alternate feature
      alternateFeature.data.coordinates = [newSemiMajorPosition.x, newSemiMajorPosition.y];
    }else{
      alternateFeature = null;
    }

    // retrieve the new semiMinor vertex.
    newSemiMinorPosition = emp.geoLibrary.geodesic_coordinate({
      x: x,
      y: y
    }, semiMinor, pointAzimuth);

    if(alternateFeature){
      newAzimuthPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMinor, -90);
    }else{
      newAzimuthPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMajor, -90);
    }
    // First update the control point with new pointer info.
    currentFeature.data.coordinates = [newSemiMinorPosition.x, newSemiMinorPosition.y];
    this.azimuth.feature.data.coordinates = [newAzimuthPosition.x, newAzimuthPosition.y];
    this.featureCopy.properties.semiMinor = semiMinor;
    items.push(this.azimuth.feature);

  }else if (featureId === this.azimuth.feature.featureId){
        // the rotation vertex was dragged.
    // get the new azimuth from the center to the current mouse position.  that
    // will determine the new vertex.

    //  asin(o/h) will return the angle in radians of a right sided triangle.
    o = pointer.lat - y;
    a = pointer.lon - x;
    h = Math.sqrt(o*o + a*a);

    // get the angle but convert to radians.
    newAzimuth = (Math.asin(o/h) * 180 / Math.PI);

    if (azimuth > 180) {
      azimuth = azimuth - 360;
    } else if (azimuth < -180) {
      azimuth = azimuth + 360;
    }

    // determine the quadrant to correctly calculate the azimuth.
    if (pointer.lat >= y &&
        pointer.lon >= x) {
      newAzimuth = 90 - newAzimuth;
    } else if (pointer.lat >= y &&
        pointer.lon < x) {
      newAzimuth = -90 + newAzimuth;
    } else if (pointer.lat <= y &&
        pointer.lon <= x) {
      newAzimuth = -90 + newAzimuth;
    } else if (pointer.lat < y &&
        pointer.lon > x) {
      newAzimuth = 90 - newAzimuth;
    }

    // compare the old azimuth to the new azimuth and get the delta.   Add the
    // delta to the old azimuth to get your new azimuth.  We need to add 90 because
    // the rotation point is 90 degrees added to azimuth.
    delta = (newAzimuth - azimuth + 90);

    newAzimuth = azimuth + delta;

    // update our copy of the feature with the new azimuth parameter.
    this.featureCopy.properties.azimuth = newAzimuth;

    // adjust the vertices
    semiMajor = emp.geoLibrary.measureDistance(this.semiMajor.feature.data.coordinates[1],
      this.semiMajor.feature.data.coordinates[0],
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

    semiMinor = emp.geoLibrary.measureDistance(this.semiMinor.feature.data.coordinates[1],
      this.semiMinor.feature.data.coordinates[0],
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

    newSemiMajorPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMajor, 90 + newAzimuth);

    newSemiMinorPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMinor, newAzimuth);

    newAzimuthPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMajor, -90 + newAzimuth);

    this.semiMajor.feature.data.coordinates = [newSemiMajorPosition.x, newSemiMajorPosition.y];
    this.semiMinor.feature.data.coordinates = [newSemiMinorPosition.x, newSemiMinorPosition.y];
    this.azimuth.feature.data.coordinates = [newAzimuthPosition.x, newAzimuthPosition.y];

    items.push(this.semiMajor.feature);
    items.push(this.semiMinor.feature);
    items.push(this.azimuth.feature);
  }

  // make sure the symbol is updated with its new properties.
  items.push(this.featureCopy);

  // Add our updated feature onto the items we will be updating in our
  // transaction.
  items.push(currentFeature);
  if(alternateFeature){
    items.push(alternateFeature);
  }

  var transaction = new emp.typeLibrary.Transaction({
      intent: emp.intents.control.FEATURE_ADD,
      mapInstanceId: this.mapInstance.mapInstanceId,
      transactionId: null,
      sender: this.mapInstance.mapInstanceId,
      originChannel: cmapi.channel.names.MAP_FEATURE_PLOT,
      source: emp.api.cmapi.SOURCE,
      messageOriginator: this.mapInstance.mapInstanceId,
      originalMessageType: cmapi.channel.names.MAP_FEATURE_PLOT,
      items: items
  });

  transaction.run();

  // Now send back the information to the editingManager that will
  // help with generating some events.
  index = this.vertices.getIndex(featureId);
  newCoordinates = [];
  newCoordinates.push({
    lat: y,
    lon: x
  });

  coordinateUpdate = {
      type: type,
      indices: [index],
      coordinates: newCoordinates
  };

  updateData.coordinateUpdate = coordinateUpdate;
  updateData.properties = this.featureCopy.properties;

  return updateData;

};

/**
 * Moves control point passed in to the new location provided.
 * Also updates the control point and the feature with the change.
 */
emp.editors.Ellipse.prototype.moveControlPoint = function(featureId, pointer) {
  // startMoveControlPoint does everything we need to do.
  return this.startMoveControlPoint(featureId, pointer);
};

/**
 * Moves control point passed in to the new location provided.
 * Also updates the control point and the feature with the change.
 */
emp.editors.Ellipse.prototype.endMoveControlPoint = function(featureId, pointer) {
  // startMoveControlPoint does everything we need to do.
  return this.startMoveControlPoint(featureId, pointer);
};

/**
 * Moves the entire feature, offsetting from the starting position.
 */
emp.editors.Ellipse.prototype.moveFeature = function() {
  // do not do anything here.  We do not want to let users move the feature.
};
<commit_msg>changed variable names and some logic....azimuth is still working as well as basic movements within the rules of semimajor/mino<commit_after>/*global*/
emp.editors = emp.editors || {};

emp.editors.Ellipse = function(args) {
  this.animation = undefined;
  this.semiMinor = undefined;
  this.semiMajor = undefined;
  this.azimuth = undefined;
  this.center = undefined;
  emp.editors.EditorBase.call(this, args);
};

emp.editors.Ellipse.prototype = Object.create(emp.editors.EditorBase.prototype);
emp.editors.Ellipse.prototype.constructor = emp.editors.Ellipse;

/**
 * Adds the control points to the map for an edit or a draw.
 */
emp.editors.Ellipse.prototype.addControlPoints = function() {

  var controlPoint,
    transaction,
    items = [],
    vertex,
    azimuth,
    semiMajor,
    semiMinor,
    semiMinorPoint,
    semiMajorPoint,
    azimuthPoint,
    semiMajorFeature,
    semiMinorFeature,
    azimuthFeature,
    semiMajorVertex,
    semiMinorVertex,
    addFeature,
    addFeature2,
    azimuthVertex,
    featureVertex,
    featureVertex2,
    x, y;

  x = this.featureCopy.data.coordinates[0];
  y = this.featureCopy.data.coordinates[1];

  // Create a feature on the map for the center of the ellipse.
  controlPoint = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    data: {
      coordinates: [x, y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.editPoint,
      iconXOffset: 12,
      iconYOffset: 12,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  // Create a vertex.  This is so the editingManager knows that
  // the center point is a control point .
  vertex = new emp.editors.Vertex(controlPoint, "vertex");
  this.vertices.push(vertex);
  this.center = vertex;
  items.push(controlPoint);

  // Creates control points.
  semiMinor = this.featureCopy.properties.semiMinor;
  semiMajor = this.featureCopy.properties.semiMajor;
  azimuth = this.featureCopy.properties.azimuth;

  semiMinorPoint = emp.geoLibrary.geodesic_coordinate({
    x: x,
    y: y
  }, semiMinor, 0 + azimuth);
  semiMajorPoint = emp.geoLibrary.geodesic_coordinate({
    x: x,
    y: y
  }, semiMajor, 90 + azimuth);
  azimuthPoint = emp.geoLibrary.geodesic_coordinate({
    x: x,
    y: y
  }, semiMajor, -90 + azimuth);

  // create a feature for each of these coordinates.

  addFeature = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    name: "Feature1",
    data: {
      coordinates: [semiMajorPoint.x, semiMajorPoint.y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.addPoint,
      iconXOffset: 8,
      iconYOffset: 8,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  addFeature2 = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    name: "Feature2",
    data: {
      coordinates: [semiMinorPoint.x, semiMinorPoint.y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.addPoint,
      iconXOffset: 8,
      iconYOffset: 8,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  azimuthFeature = new emp.typeLibrary.Feature({
    overlayId: "vertices",
    featureId: emp3.api.createGUID(),
    format: emp3.api.enums.FeatureTypeEnum.GEO_POINT,
    name: "azimuth",
    data: {
      coordinates: [azimuthPoint.x, azimuthPoint.y],
      type: 'Point'
    },
    properties: {
      iconUrl: emp.ui.images.rotationPoint,
      iconXOffset: 12,
      iconYOffset: 12,
      xUnits: "pixels",
      yUnits: "pixels",
      altitudeMode: cmapi.enums.altitudeMode.CLAMP_TO_GROUND
    }
  });

  items.push(addFeature);
  items.push(addFeature2);
  items.push(azimuthFeature);

  featureVertex = new emp.editors.Vertex(addFeature, "add");
  featureVertex2 = new emp.editors.Vertex(addFeature2, "add");
  azimuthVertex = new emp.editors.Vertex(azimuthFeature, "add");

  this.semiMajor = featureVertex;
  this.semiMinor = featureVertex2;
  this.azimuth = azimuthVertex;

  this.vertices.push(featureVertex);
  this.vertices.push(featureVertex2);
  this.vertices.push(azimuthVertex);

  // run the transaction and add all the symbols on the map.
  transaction = new emp.typeLibrary.Transaction({
    intent: emp.intents.control.FEATURE_ADD,
    mapInstanceId: this.mapInstance.mapInstanceId,
    transactionId: null,
    sender: this.mapInstance.mapInstanceId,
    originChannel: cmapi.channel.names.MAP_FEATURE_PLOT,
    source: emp.api.cmapi.SOURCE,
    messageOriginator: this.mapInstance.mapInstanceId,
    originalMessageType: cmapi.channel.names.MAP_FEATURE_PLOT,
    items: items
  });
  transaction.run();

};

/**
 * Begins the movement of a control point.
 */
emp.editors.Ellipse.prototype.startMoveControlPoint = function(featureId, pointer) {

  var currentFeature,
    currentVertex,
    azimuth,
    pointAzimuth,
    items = [],
    semiMajor,
    semiMinor,
    azimuth,
    newSemiMajorPosition,
    newSemiMinorPosition,
    semiMajorCoreId,
    semiMajorFeatureId,
    semiMinorCoreId,
    semiMinorFeatureId,
    alternateVertex,
    alternateFeature,
    index,
    coordinateUpdate,
    updateData = {},
    type = emp.typeLibrary.CoordinateUpdateType.UPDATE,
    newCoordinates,
    x, y;

  x = this.featureCopy.data.coordinates[0];
  y = this.featureCopy.data.coordinates[1];

  currentVertex = this.vertices.find(featureId);
  currentFeature = currentVertex.feature;

  azimuth = this.featureCopy.properties.azimuth;

  // Calculate the new position of where
  // we want the control point to be.
  if (featureId === this.semiMajor.feature.featureId){
    // measure the distance between the mouse location and the center.  This
    // will be the new semiMajor.
    distance = emp.geoLibrary.measureDistance(pointer.lat,
      pointer.lon,
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

    //ensure that semiMajor is always greater than semiMinor
    if(distance < this.featureCopy.properties.semiMinor){
      this.featureCopy.properties.semiMajor = this.featureCopy.properties.semiMinor;
      this.featureCopy.properties.semiMinor = distance;
    }else{
      this.featureCopy.properties.semiMajor = distance;
    }

    // retrieve the new semiMajor vertex.
    newFeaturePosition = emp.geoLibrary.geodesic_coordinate({
      x: x,
      y: y
    }, distance, 90 + azimuth);

    newAzimuthPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, distance, -90 + azimuth);

    // First update the control point with new pointer info.
    currentFeature.data.coordinates = [newFeaturePosition.x, newFeaturePosition.y];
    this.azimuth.feature.data.coordinates = [newAzimuthPosition.x, newAzimuthPosition.y];

    items.push(this.azimuth.feature);

  }else if (featureId === this.semiMinor.feature.featureId){
    // measure the distance between the mouse location and the center.  This
    // will be the new semiMinor.
    distance = emp.geoLibrary.measureDistance(pointer.lat,
      pointer.lon,
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

        //ensure that semiMajor is always greater than semiMinor
    if(distance > this.featureCopy.properties.semiMajor){
      this.featureCopy.properties.semiMinor = this.featureCopy.properties.semiMajor;
      this.featureCopy.properties.semiMajor = distance;
    }else{
      this.featureCopy.properties.semiMinor = distance;
    }

    // retrieve the new semiMinor vertex.
    newFeaturePosition = emp.geoLibrary.geodesic_coordinate({
      x: x,
      y: y
    }, distance, azimuth);

    // First update the control point with new pointer info.
    currentFeature.data.coordinates = [newFeaturePosition.x, newFeaturePosition.y];

  }else if (featureId === this.azimuth.feature.featureId){
        // the rotation vertex was dragged.
    // get the new azimuth from the center to the current mouse position.  that
    // will determine the new vertex.

    //  asin(o/h) will return the angle in radians of a right sided triangle.
    o = pointer.lat - y;
    a = pointer.lon - x;
    h = Math.sqrt(o*o + a*a);

    // get the angle but convert to radians.
    newAzimuth = (Math.asin(o/h) * 180 / Math.PI);

    if (azimuth > 180) {
      azimuth = azimuth - 360;
    } else if (azimuth < -180) {
      azimuth = azimuth + 360;
    }

    // determine the quadrant to correctly calculate the azimuth.
    if (pointer.lat >= y &&
        pointer.lon >= x) {
      newAzimuth = 90 - newAzimuth;
    } else if (pointer.lat >= y &&
        pointer.lon < x) {
      newAzimuth = -90 + newAzimuth;
    } else if (pointer.lat <= y &&
        pointer.lon <= x) {
      newAzimuth = -90 + newAzimuth;
    } else if (pointer.lat < y &&
        pointer.lon > x) {
      newAzimuth = 90 - newAzimuth;
    }

    // compare the old azimuth to the new azimuth and get the delta.   Add the
    // delta to the old azimuth to get your new azimuth.  We need to add 90 because
    // the rotation point is 90 degrees added to azimuth.
    delta = (newAzimuth - azimuth + 90);

    newAzimuth = azimuth + delta;

    // update our copy of the feature with the new azimuth parameter.
    this.featureCopy.properties.azimuth = newAzimuth;

    // adjust the vertices
    semiMajor = emp.geoLibrary.measureDistance(this.semiMajor.feature.data.coordinates[1],
      this.semiMajor.feature.data.coordinates[0],
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

    semiMinor = emp.geoLibrary.measureDistance(this.semiMinor.feature.data.coordinates[1],
      this.semiMinor.feature.data.coordinates[0],
      this.center.feature.data.coordinates[1],
      this.center.feature.data.coordinates[0], "meters");

    newSemiMajorPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMajor, 90 + newAzimuth);

    newSemiMinorPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMinor, newAzimuth);

    newAzimuthPosition = emp.geoLibrary.geodesic_coordinate({
        x: x,
        y: y
      }, semiMajor, -90 + newAzimuth);

    this.semiMajor.feature.data.coordinates = [newSemiMajorPosition.x, newSemiMajorPosition.y];
    this.semiMinor.feature.data.coordinates = [newSemiMinorPosition.x, newSemiMinorPosition.y];
    this.azimuth.feature.data.coordinates = [newAzimuthPosition.x, newAzimuthPosition.y];

    items.push(this.semiMajor.feature);
    items.push(this.semiMinor.feature);
    items.push(this.azimuth.feature);
  }

  // make sure the symbol is updated with its new properties.
  items.push(this.featureCopy);

  // Add our updated feature onto the items we will be updating in our
  // transaction.
  items.push(currentFeature);

  var transaction = new emp.typeLibrary.Transaction({
      intent: emp.intents.control.FEATURE_ADD,
      mapInstanceId: this.mapInstance.mapInstanceId,
      transactionId: null,
      sender: this.mapInstance.mapInstanceId,
      originChannel: cmapi.channel.names.MAP_FEATURE_PLOT,
      source: emp.api.cmapi.SOURCE,
      messageOriginator: this.mapInstance.mapInstanceId,
      originalMessageType: cmapi.channel.names.MAP_FEATURE_PLOT,
      items: items
  });

  transaction.run();

  // Now send back the information to the editingManager that will
  // help with generating some events.
  index = this.vertices.getIndex(featureId);
  newCoordinates = [];
  newCoordinates.push({
    lat: y,
    lon: x
  });

  coordinateUpdate = {
      type: type,
      indices: [index],
      coordinates: newCoordinates
  };

  updateData.coordinateUpdate = coordinateUpdate;
  updateData.properties = this.featureCopy.properties;

  return updateData;

};

/**
 * Moves control point passed in to the new location provided.
 * Also updates the control point and the feature with the change.
 */
emp.editors.Ellipse.prototype.moveControlPoint = function(featureId, pointer) {
  // startMoveControlPoint does everything we need to do.
  return this.startMoveControlPoint(featureId, pointer);
};

/**
 * Moves control point passed in to the new location provided.
 * Also updates the control point and the feature with the change.
 */
emp.editors.Ellipse.prototype.endMoveControlPoint = function(featureId, pointer) {
  // startMoveControlPoint does everything we need to do.
  return this.startMoveControlPoint(featureId, pointer);
};

/**
 * Moves the entire feature, offsetting from the starting position.
 */
emp.editors.Ellipse.prototype.moveFeature = function() {
  // do not do anything here.  We do not want to let users move the feature.
};
<|endoftext|>


<commit_before>import {
  ATTRIBUTES_KEY_SYMBOL,
  COMPONENTS_IMPLEMENTATION_MAP,
  DOM_COMPONENT_INSTANCE_PROPERTY,
  PLUGINS_SET,
  SLOTS_KEY_SYMBOL,
  TEMPLATE_KEY_SYMBOL
} from '../globals'
import {DOMattributesToObject, getName} from '../utils/dom'
import {
  autobindMethods,
  callOrAssign,
  camelToDashCase,
  defineDefaults,
  defineProperties,
  defineProperty,
  evaluateAttributeExpressions,
  noop,
  panic
} from '../utils/misc'
import {bindingTypes, template as createTemplate, expressionTypes} from '@riotjs/dom-bindings'
import $ from 'bianco.query'
import createSlots from './slots'
import cssManager from './css-manager'
import curry from 'curri'
import {isFunction} from '../utils/checks'
import {set as setAttr} from 'bianco.attr'

const COMPONENT_CORE_HELPERS = Object.freeze({
  // component helpers
  $(selector){ return $(selector, this.root)[0] || null },
  $$(selector){ return $(selector, this.root) }
})

const COMPONENT_LIFECYCLE_METHODS = Object.freeze({
  shouldUpdate: noop,
  onBeforeMount: noop,
  onMounted: noop,
  onBeforeUpdate: noop,
  onUpdated: noop,
  onBeforeUnmount: noop,
  onUnmounted: noop
})

const MOCKED_TEMPLATE_INTERFACE = {
  update: noop,
  mount: noop,
  unmount: noop,
  clone: noop,
  createDOM: noop
}

/**
 * Create the component interface needed for the compiled components
 * @param   {string} options.css - component css
 * @param   {Function} options.template - functon that will return the dom-bindings template function
 * @param   {Object} options.tag - component interface
 * @param   {string} options.name - component name
 * @returns {Object} component like interface
 */
export function createComponent({css, template, tag, name}) {
  return ({slots, attributes, props}) => {
    const component = defineComponent({
      css,
      template,
      tag,
      name
    })({slots, attributes, props})

    return {
      mount(element, parentScope, state) {
        return component.mount(element, state, parentScope)
      },
      update(parentScope, state) {
        return component.update(state, parentScope)
      },
      unmount(preserveRoot) {
        return component.unmount(preserveRoot)
      }
    }
  }
}

/**
 * Component definition function
 * @param   {Object} implementation - the componen implementation will be generated via compiler
 * @param   {Object} component - the component initial properties
 * @returns {Object} a new component implementation object
 */
export function defineComponent({css, template, tag, name}) {
  const componentAPI = callOrAssign(tag) || {}
  const components = createSubcomponents(componentAPI.components)

  // add the component css into the DOM
  if (css && name) cssManager.add(name, css)

  return curry(enhanceComponentAPI)(defineProperties(
    // set the component defaults without overriding the original component API
    defineDefaults(componentAPI, {
      ...COMPONENT_LIFECYCLE_METHODS,
      state: {}
    }), {
      // defined during the component creation
      slots: null,
      root: null,
      // these properties should not be overriden
      ...COMPONENT_CORE_HELPERS,
      name,
      css,
      template: template ? template(
        createTemplate,
        expressionTypes,
        bindingTypes,
        name => {
          return components[name] || COMPONENTS_IMPLEMENTATION_MAP.get(name)
        }
      ) : MOCKED_TEMPLATE_INTERFACE
    })
  )
}

/**
 * Evaluate the component properties either from its real attributes or from its attribute expressions
 * @param   {HTMLElement} element - component root
 * @param   {Array}  attributeExpressions - attribute expressions generated by the riot compiler
 * @param   {Object} scope - current scope
 * @param   {Object} currentProps - current component properties
 * @returns {Object} attributes key value pairs
 */
function evaluateProps(element, attributeExpressions = [], scope, currentProps = {}) {
  if (attributeExpressions.length) {
    return scope ? evaluateAttributeExpressions(attributeExpressions, scope) : currentProps
  }

  return DOMattributesToObject(element)
}

/**
 * Create the bindings to update the component attributes
 * @param   {Array} attributes - list of attribute bindings
 * @returns {TemplateChunk} - template bindings object
 */
function createAttributeBindings(attributes) {
  return createTemplate(null, [{
    expressions: (attributes || []).map(attr => {
      return {
        type: expressionTypes.ATTRIBUTE,
        ...attr
      }
    })
  }])
}

/**
 * Create the subcomponents that can be included inside a tag in runtime
 * @param   {Object} components - components imported in runtime
 * @returns {Object} all the components transformed into Riot.Component factory functions
 */
function createSubcomponents(components = {}) {
  return Object.entries(callOrAssign(components))
    .reduce((acc, [key, value]) => {
      acc[camelToDashCase(key)] = createComponent(value)
      return acc
    }, {})
}

/**
 * Run the component instance through all the plugins set by the user
 * @param   {Object} component - component instance
 * @returns {Object} the component enhanced by the plugins
 */
function runPlugins(component) {
  return [...PLUGINS_SET].reduce((c, fn) => fn(c) || c, component)
}

/**
 * Compute the component current state merging it with its previous state
 * @param   {Object} oldState - previous state object
 * @param   {Object} newState - new state givent to the `update` call
 * @returns {Object} new object state
 */
function computeState(oldState, newState) {
  return {
    ...oldState,
    ...callOrAssign(newState)
  }
}

/**
 * Add eventually the "is" attribute to link this DOM node to its css
 * @param {HTMLElement} element - target root node
 * @param {string} name - name of the component mounted
 * @returns {undefined} it's a void function
 */
function addCssHook(element, name) {
  if (getName(element) !== name) {
    setAttr(element, 'is', name)
  }
}

/**
 * Component creation factory function that will enhance the user provided API
 * @param   {Object} component - a component implementation previously defined
 * @param   {Array} options.slots - component slots generated via riot compiler
 * @param   {Array} options.attributes - attribute expressions generated via riot compiler
 * @returns {Riot.Component} a riot component instance
 */
export function enhanceComponentAPI(component, {slots, attributes, props}) {
  const attributeBindings = createAttributeBindings(attributes)

  return autobindMethods(
    runPlugins(
      defineProperties(Object.create(component), {
        mount(element, state = {}, parentScope) {
          this.props = {
            ...props,
            ...evaluateProps(element, attributes, parentScope)
          }

          this.state = computeState(this.state, state)

          this[TEMPLATE_KEY_SYMBOL] = this.template.createDOM(element).clone()
          this[ATTRIBUTES_KEY_SYMBOL] = attributeBindings.createDOM(element).clone()

          // link this object to the DOM node
          element[DOM_COMPONENT_INSTANCE_PROPERTY] = this
          // add eventually the 'is' attribute
          component.name && addCssHook(element, component.name)
          // define the root element
          defineProperty(this, 'root', element)

          // before mount lifecycle event
          this.onBeforeMount(this.state, this.props)

          // handlte the template and its attributes
          this[ATTRIBUTES_KEY_SYMBOL].mount(element, parentScope)
          this[TEMPLATE_KEY_SYMBOL].mount(element, this)
          // create the slots and mount them
          this[SLOTS_KEY_SYMBOL] = createSlots(element, slots || []).mount(parentScope)

          this.onMounted(this.state, this.props)

          return this
        },
        update(state = {}, parentScope) {
          const newProps = evaluateProps(this.root, attributes, parentScope, this.props)

          if (this.shouldUpdate(newProps, this.props) === false) return

          this.props = newProps
          this.state = computeState(this.state, state)

          this.onBeforeUpdate(this.state, this.props)

          if (parentScope) {
            this[ATTRIBUTES_KEY_SYMBOL].update(parentScope)
            this[SLOTS_KEY_SYMBOL].update(parentScope)
          }

          this[TEMPLATE_KEY_SYMBOL].update(this)
          this.onUpdated(this.state, this.props)

          return this
        },
        unmount(preserveRoot) {
          this.onBeforeUnmount(this.state, this.props)
          this[ATTRIBUTES_KEY_SYMBOL].unmount()
          this[SLOTS_KEY_SYMBOL].unmount()
          this[TEMPLATE_KEY_SYMBOL].unmount(this, !preserveRoot)
          this.onUnmounted(this.state, this.props)

          return this
        }
      })
    ),
    Object.keys(component).filter(prop => isFunction(component[prop]))
  )
}

/**
 * Component initialization function starting from a DOM node
 * @param   {HTMLElement} element - element to upgrade
 * @param   {Object} initialProps - initial component state
 * @param   {string} componentName - component id
 * @returns {Object} a new component instance bound to a DOM node
 */
export function mountComponent(element, initialProps, componentName) {
  const name = componentName || getName(element)
  if (!COMPONENTS_IMPLEMENTATION_MAP.has(name)) panic(`The component named "${name}" was never registered`)

  const component = COMPONENTS_IMPLEMENTATION_MAP.get(name)({ props: initialProps })

  return component.mount(element)
}
<commit_msg>added: comment on the create component method<commit_after>import {
  ATTRIBUTES_KEY_SYMBOL,
  COMPONENTS_IMPLEMENTATION_MAP,
  DOM_COMPONENT_INSTANCE_PROPERTY,
  PLUGINS_SET,
  SLOTS_KEY_SYMBOL,
  TEMPLATE_KEY_SYMBOL
} from '../globals'
import {DOMattributesToObject, getName} from '../utils/dom'
import {
  autobindMethods,
  callOrAssign,
  camelToDashCase,
  defineDefaults,
  defineProperties,
  defineProperty,
  evaluateAttributeExpressions,
  noop,
  panic
} from '../utils/misc'
import {bindingTypes, template as createTemplate, expressionTypes} from '@riotjs/dom-bindings'
import $ from 'bianco.query'
import createSlots from './slots'
import cssManager from './css-manager'
import curry from 'curri'
import {isFunction} from '../utils/checks'
import {set as setAttr} from 'bianco.attr'

const COMPONENT_CORE_HELPERS = Object.freeze({
  // component helpers
  $(selector){ return $(selector, this.root)[0] || null },
  $$(selector){ return $(selector, this.root) }
})

const COMPONENT_LIFECYCLE_METHODS = Object.freeze({
  shouldUpdate: noop,
  onBeforeMount: noop,
  onMounted: noop,
  onBeforeUpdate: noop,
  onUpdated: noop,
  onBeforeUnmount: noop,
  onUnmounted: noop
})

const MOCKED_TEMPLATE_INTERFACE = {
  update: noop,
  mount: noop,
  unmount: noop,
  clone: noop,
  createDOM: noop
}

/**
 * Create the component interface needed for the @riotjs/dom-bindings tag bindings
 * @param   {string} options.css - component css
 * @param   {Function} options.template - functon that will return the dom-bindings template function
 * @param   {Object} options.tag - component interface
 * @param   {string} options.name - component name
 * @returns {Object} component like interface
 */
export function createComponent({css, template, tag, name}) {
  return ({slots, attributes, props}) => {
    const component = defineComponent({
      css,
      template,
      tag,
      name
    })({slots, attributes, props})

    // notice that for the components create via tag binding
    // we need to invert the mount (state/parentScope) arguments
    // the template bindings will only forward the parentScope updates
    // and never deal with the component state
    return {
      mount(element, parentScope, state) {
        return component.mount(element, state, parentScope)
      },
      update(parentScope, state) {
        return component.update(state, parentScope)
      },
      unmount(preserveRoot) {
        return component.unmount(preserveRoot)
      }
    }
  }
}

/**
 * Component definition function
 * @param   {Object} implementation - the componen implementation will be generated via compiler
 * @param   {Object} component - the component initial properties
 * @returns {Object} a new component implementation object
 */
export function defineComponent({css, template, tag, name}) {
  const componentAPI = callOrAssign(tag) || {}
  const components = createSubcomponents(componentAPI.components)

  // add the component css into the DOM
  if (css && name) cssManager.add(name, css)

  return curry(enhanceComponentAPI)(defineProperties(
    // set the component defaults without overriding the original component API
    defineDefaults(componentAPI, {
      ...COMPONENT_LIFECYCLE_METHODS,
      state: {}
    }), {
      // defined during the component creation
      slots: null,
      root: null,
      // these properties should not be overriden
      ...COMPONENT_CORE_HELPERS,
      name,
      css,
      template: template ? template(
        createTemplate,
        expressionTypes,
        bindingTypes,
        name => {
          return components[name] || COMPONENTS_IMPLEMENTATION_MAP.get(name)
        }
      ) : MOCKED_TEMPLATE_INTERFACE
    })
  )
}

/**
 * Evaluate the component properties either from its real attributes or from its attribute expressions
 * @param   {HTMLElement} element - component root
 * @param   {Array}  attributeExpressions - attribute expressions generated by the riot compiler
 * @param   {Object} scope - current scope
 * @param   {Object} currentProps - current component properties
 * @returns {Object} attributes key value pairs
 */
function evaluateProps(element, attributeExpressions = [], scope, currentProps = {}) {
  if (attributeExpressions.length) {
    return scope ? evaluateAttributeExpressions(attributeExpressions, scope) : currentProps
  }

  return DOMattributesToObject(element)
}

/**
 * Create the bindings to update the component attributes
 * @param   {Array} attributes - list of attribute bindings
 * @returns {TemplateChunk} - template bindings object
 */
function createAttributeBindings(attributes) {
  return createTemplate(null, [{
    expressions: (attributes || []).map(attr => {
      return {
        type: expressionTypes.ATTRIBUTE,
        ...attr
      }
    })
  }])
}

/**
 * Create the subcomponents that can be included inside a tag in runtime
 * @param   {Object} components - components imported in runtime
 * @returns {Object} all the components transformed into Riot.Component factory functions
 */
function createSubcomponents(components = {}) {
  return Object.entries(callOrAssign(components))
    .reduce((acc, [key, value]) => {
      acc[camelToDashCase(key)] = createComponent(value)
      return acc
    }, {})
}

/**
 * Run the component instance through all the plugins set by the user
 * @param   {Object} component - component instance
 * @returns {Object} the component enhanced by the plugins
 */
function runPlugins(component) {
  return [...PLUGINS_SET].reduce((c, fn) => fn(c) || c, component)
}

/**
 * Compute the component current state merging it with its previous state
 * @param   {Object} oldState - previous state object
 * @param   {Object} newState - new state givent to the `update` call
 * @returns {Object} new object state
 */
function computeState(oldState, newState) {
  return {
    ...oldState,
    ...callOrAssign(newState)
  }
}

/**
 * Add eventually the "is" attribute to link this DOM node to its css
 * @param {HTMLElement} element - target root node
 * @param {string} name - name of the component mounted
 * @returns {undefined} it's a void function
 */
function addCssHook(element, name) {
  if (getName(element) !== name) {
    setAttr(element, 'is', name)
  }
}

/**
 * Component creation factory function that will enhance the user provided API
 * @param   {Object} component - a component implementation previously defined
 * @param   {Array} options.slots - component slots generated via riot compiler
 * @param   {Array} options.attributes - attribute expressions generated via riot compiler
 * @returns {Riot.Component} a riot component instance
 */
export function enhanceComponentAPI(component, {slots, attributes, props}) {
  const attributeBindings = createAttributeBindings(attributes)

  return autobindMethods(
    runPlugins(
      defineProperties(Object.create(component), {
        mount(element, state = {}, parentScope) {
          this.props = {
            ...props,
            ...evaluateProps(element, attributes, parentScope)
          }

          this.state = computeState(this.state, state)

          this[TEMPLATE_KEY_SYMBOL] = this.template.createDOM(element).clone()
          this[ATTRIBUTES_KEY_SYMBOL] = attributeBindings.createDOM(element).clone()

          // link this object to the DOM node
          element[DOM_COMPONENT_INSTANCE_PROPERTY] = this
          // add eventually the 'is' attribute
          component.name && addCssHook(element, component.name)
          // define the root element
          defineProperty(this, 'root', element)

          // before mount lifecycle event
          this.onBeforeMount(this.state, this.props)

          // handlte the template and its attributes
          this[ATTRIBUTES_KEY_SYMBOL].mount(element, parentScope)
          this[TEMPLATE_KEY_SYMBOL].mount(element, this)
          // create the slots and mount them
          this[SLOTS_KEY_SYMBOL] = createSlots(element, slots || []).mount(parentScope)

          this.onMounted(this.state, this.props)

          return this
        },
        update(state = {}, parentScope) {
          const newProps = evaluateProps(this.root, attributes, parentScope, this.props)

          if (this.shouldUpdate(newProps, this.props) === false) return

          this.props = newProps
          this.state = computeState(this.state, state)

          this.onBeforeUpdate(this.state, this.props)

          if (parentScope) {
            this[ATTRIBUTES_KEY_SYMBOL].update(parentScope)
            this[SLOTS_KEY_SYMBOL].update(parentScope)
          }

          this[TEMPLATE_KEY_SYMBOL].update(this)
          this.onUpdated(this.state, this.props)

          return this
        },
        unmount(preserveRoot) {
          this.onBeforeUnmount(this.state, this.props)
          this[ATTRIBUTES_KEY_SYMBOL].unmount()
          this[SLOTS_KEY_SYMBOL].unmount()
          this[TEMPLATE_KEY_SYMBOL].unmount(this, !preserveRoot)
          this.onUnmounted(this.state, this.props)

          return this
        }
      })
    ),
    Object.keys(component).filter(prop => isFunction(component[prop]))
  )
}

/**
 * Component initialization function starting from a DOM node
 * @param   {HTMLElement} element - element to upgrade
 * @param   {Object} initialProps - initial component state
 * @param   {string} componentName - component id
 * @returns {Object} a new component instance bound to a DOM node
 */
export function mountComponent(element, initialProps, componentName) {
  const name = componentName || getName(element)
  if (!COMPONENTS_IMPLEMENTATION_MAP.has(name)) panic(`The component named "${name}" was never registered`)

  const component = COMPONENTS_IMPLEMENTATION_MAP.get(name)({ props: initialProps })

  return component.mount(element)
}
<|endoftext|>


<commit_before>/**
 * A FreeDOM interface to WebRTC Peer Connections
 * @param Channel channel a channel for emitting events.
 * @constructor
 * @private
 */
var PeerConnection_unprivileged = function(channel) {
  this.appChannel = channel;
  this.dataChannel = null;
  this.identity = null;
  this.connection = null;
  this.myPid = Math.random();
  this.remotePid = 1;
  handleEvents(this);
};

PeerConnection_unprivileged.prototype.open = function(proxy, continuation) {
  if (this.connection) {
    continuation(false);
  }

  // Listen for messages to/from the provided message channel.
  this.appChannel = Core_unprivileged.bindChannel(this.appChannel.app, proxy);
  this.appChannel['on']('message', this.onIdentity.bind(this));
  this.appChannel.postMessage({
    'type': 'ready',
    'action': 'event'
  });

  this.setup(true);
  continuation();
};

PeerConnection_unprivileged.prototype.setup = function(initiate) {
  var RTCPeerConnection = RTCPeerConnection || webkitRTCPeerConnection || mozRTCPeerConnection;
  this.connection = new RTCPeerConnection(null, {'optional': [{'RtpDataChannels': true}]});

  var dcSetup = function() {
    this.dataChannel.addEventListener('open', function() {
      console.log("Data channel opened.");
      this.emit('open');
    }.bind(this), true);
    this.dataChannel.addEventListener('message', function(m) {
      // TODO(willscott): Support native binary transport, rather than this mess
      if (this.parts > 0) {
        this.buf += m.data;
        this.parts--;
        console.log('waiting for ' + this.parts + ' more parts.');
        if (this.parts === 0) {
          console.log("binary data recieved (" + this.buf.length + " bytes)");
          var databuf = JSON.parse(this.buf);
          var arr = new Uint8Array(databuf['binary']);
          var blob = new Blob([arr.buffer], {"type": databuf['mime']});
          this['dispatchEvent']('message', {"binary": blob});
          this.buf = "";
        }
        return;
      }
      var data = JSON.parse(m.data);
      if (data['text']) {
        this['dispatchEvent']('message', {"text": data['text']});
      } else {
        this.parts = data['binary'];
        console.log("Beginning receipt of binary data (" + this.parts + " parts)");
        this.buf = "";
      }
    }.bind(this), true);
    this.dataChannel.addEventListener('close', function(conn) {
      if (this.connection == conn) {
        this['dispatchEvent']('onClose');
        this.close(function() {});
      }
    }.bind(this, this.connection), true);
  }.bind(this);

  if (initiate) {
    this.dataChannel = this.connection.createDataChannel("sendChannel", {'reliable': false});
    dcSetup();
  } else {
    this.connection.addEventListener('datachannel', function(evt) {
      this.dataChannel = evt['channel'];
      dcSetup();
    }.bind(this));
  }

  this.connection.addEventListener('icecandidate', function(evt) {
    if(evt && evt['candidate']) {
      this.appChannel.postMessage({
        'type': 'message',
        'action': 'event',
        'data': JSON.stringify(evt['candidate'])
      });
    }
  }.bind(this), true);

  this.makeOffer();
};

PeerConnection_unprivileged.prototype.makeOffer = function() {
  if (this.remotePid < this.myPid) {
    return;
  }
  this.connection.createOffer(function(desc) {
    this.connection.setLocalDescription(desc);
    desc['pid'] = this.myPid;
    this.appChannel.postMessage({
      'type': 'message',
      'action': 'event',
      'data': JSON.stringify(desc)
    });
  }.bind(this));
};

PeerConnection_unprivileged.prototype.makeAnswer = function() {
  this.connection.createAnswer(function(desc) {
    this.connection.setLocalDescription(desc);
    desc['pid'] = this.myPid;
    this.appChannel.postMessage({
      'type': 'message',
      'action': 'event',
      'data': JSON.stringify(desc)
    });
  }.bind(this));
};

PeerConnection_unprivileged.prototype.onIdentity = function(msg) {
  try {
    var m = JSON.parse(msg.data);
    if (m['candidate']) {
      var candidate = new RTCIceCandidate(m);
      this.connection.addIceCandidate(candidate);
    } else if (m['type'] == 'offer' && m['pid'] != this.myId) {
      this.remotePid = m['pid'];
      if (this.remotePid < this.myPid) {
        this.close(function() {
          this.setup(false);
          this.connection.setRemoteDescription(new RTCSessionDescription(m), function() {}, function() {
            console.log("Failed to set remote description");
          });
          this.makeAnswer();
        }.bind(this));
      } else {
        // They'll get my offer and send an answer.
      }
    } else if (m['type'] == 'answer' && m['pid'] != this.myId) {
      this.remotePid = m['pid'];
      this.connection.setRemoteDescription(new RTCSessionDescription(m));
    }
  } catch(e) {
    console.log("Couldn't understand identity message: " + JSON.stringify(msg) + ": -> " + e.message);
  }
};

PeerConnection_unprivileged.prototype.postMessage = function(ref, continuation) {
  if (!this.connection) {
    return continuation(false);
  }
  // Queue until open.
  if (!this.dataChannel || this.dataChannel.readyState != "open") {
    return this.once('open', this.postMessage.bind(this, ref, continuation));
  }
  window.dc = this.dataChannel;

  console.log("Sending transport data.");
  if(ref['text']) {
    console.log("Sending text: " + ref['text']);
    this.dataChannel.send(JSON.stringify({"text":ref['text']}));
  } else if(ref['binary']) {
    // TODO(willscott): implement direct blob support when available.
    console.log("Transmitting " + ref['binary'].size + " binary bytes");
    var reader = new FileReader();
    reader.addEventListener('load', function(type, ev) {
      var arr = [];
      arr.push.apply(arr, new Uint8Array(ev.target.result));
      // Chunk messages so that packets are below MTU.
      var MAX_LEN = 512;
      var STEP = 300;
      var str = JSON.stringify({"mime": type, "binary": arr});
      var parts = Math.ceil(str.length / MAX_LEN);
      console.log("Sending chunked " + type + " ("+ str.length + " bytes)");
      this.dataChannel.send(JSON.stringify({"binary": parts}));

      var delay = 0;
      var sendPart = function(x) {
        this.dataChannel.send(x);
      };

      while (str.length > 0) {
        setTimeout(sendPart.bind(this, str.substr(0, MAX_LEN)), delay);
        delay += STEP;
        str = str.substr(MAX_LEN);
      }
    }.bind(this, ref['binary'].type), true);

    reader.readAsArrayBuffer(ref['binary']);
  }
  continuation();
};

PeerConnection_unprivileged.prototype.close = function(continuation) {
  delete this.dataChannel;

  if (this.connection) {
    try {
      this.connection.close();
    } catch(e) {
      // Ignore already-closed errors.
    }
    delete this.connection;
  }
  continuation();
};

fdom.apis.register("core.peerconnection", PeerConnection_unprivileged);
<commit_msg>messages weren't always getting serialized in transit<commit_after>/**
 * A FreeDOM interface to WebRTC Peer Connections
 * @param Channel channel a channel for emitting events.
 * @constructor
 * @private
 */
var PeerConnection_unprivileged = function(channel) {
  this.appChannel = channel;
  this.dataChannel = null;
  this.identity = null;
  this.connection = null;
  this.myPid = Math.random();
  this.remotePid = 1;
  handleEvents(this);
};

PeerConnection_unprivileged.prototype.open = function(proxy, continuation) {
  if (this.connection) {
    continuation(false);
  }

  // Listen for messages to/from the provided message channel.
  this.appChannel = Core_unprivileged.bindChannel(this.appChannel.app, proxy);
  this.appChannel['on']('message', this.onIdentity.bind(this));
  this.appChannel.postMessage({
    'type': 'ready',
    'action': 'event'
  });

  this.setup(true);
  continuation();
};

PeerConnection_unprivileged.prototype.setup = function(initiate) {
  var RTCPeerConnection = RTCPeerConnection || webkitRTCPeerConnection || mozRTCPeerConnection;
  this.connection = new RTCPeerConnection(null, {'optional': [{'RtpDataChannels': true}]});

  var dcSetup = function() {
    this.dataChannel.addEventListener('open', function() {
      console.log("Data channel opened.");
      this.emit('open');
    }.bind(this), true);
    this.dataChannel.addEventListener('message', function(m) {
      // TODO(willscott): Support native binary transport, rather than this mess
      if (this.parts > 0) {
        this.buf += m.data;
        this.parts--;
        console.log('waiting for ' + this.parts + ' more parts.');
        if (this.parts === 0) {
          console.log("binary data recieved (" + this.buf.length + " bytes)");
          var databuf = JSON.parse(this.buf);
          var arr = new Uint8Array(databuf['binary']);
          var blob = new Blob([arr.buffer], {"type": databuf['mime']});
          this['dispatchEvent']('message', {"binary": blob});
          this.buf = "";
        }
        return;
      }
      var data = JSON.parse(m.data);
      if (data['text']) {
        this['dispatchEvent']('message', {"text": data['text']});
      } else {
        this.parts = data['binary'];
        console.log("Beginning receipt of binary data (" + this.parts + " parts)");
        this.buf = "";
      }
    }.bind(this), true);
    this.dataChannel.addEventListener('close', function(conn) {
      if (this.connection == conn) {
        this['dispatchEvent']('onClose');
        this.close(function() {});
      }
    }.bind(this, this.connection), true);
  }.bind(this);

  if (initiate) {
    this.dataChannel = this.connection.createDataChannel("sendChannel", {'reliable': false});
    dcSetup();
  } else {
    this.connection.addEventListener('datachannel', function(evt) {
      this.dataChannel = evt['channel'];
      dcSetup();
    }.bind(this));
  }

  this.connection.addEventListener('icecandidate', function(evt) {
    if(evt && evt['candidate']) {
      this.appChannel.postMessage({
        'type': 'message',
        'action': 'event',
        'data': JSON.stringify(evt['candidate'])
      });
    }
  }.bind(this), true);

  this.makeOffer();
};

PeerConnection_unprivileged.prototype.makeOffer = function() {
  if (this.remotePid < this.myPid) {
    return;
  }
  this.connection.createOffer(function(desc) {
    this.connection.setLocalDescription(desc);
    desc['pid'] = this.myPid;
    this.appChannel.postMessage({
      'type': 'message',
      'action': 'event',
      'data': JSON.stringify(desc)
    });
  }.bind(this));
};

PeerConnection_unprivileged.prototype.makeAnswer = function() {
  this.connection.createAnswer(function(desc) {
    this.connection.setLocalDescription(desc);
    desc['pid'] = this.myPid;
    this.appChannel.postMessage({
      'type': 'message',
      'action': 'event',
      'data': JSON.stringify(desc)
    });
  }.bind(this));
};

PeerConnection_unprivileged.prototype.onIdentity = function(msg) {
  try {
    var m = msg.data;
    if (typeof msg.data === "string") {
      m = JSON.parse(msg.data);
    }
    if (m['candidate']) {
      var candidate = new RTCIceCandidate(m);
      this.connection.addIceCandidate(candidate);
    } else if (m['type'] == 'offer' && m['pid'] != this.myId) {
      this.remotePid = m['pid'];
      if (this.remotePid < this.myPid) {
        this.close(function() {
          this.setup(false);
          this.connection.setRemoteDescription(new RTCSessionDescription(m), function() {}, function() {
            console.log("Failed to set remote description");
          });
          this.makeAnswer();
        }.bind(this));
      } else {
        // They'll get my offer and send an answer.
      }
    } else if (m['type'] == 'answer' && m['pid'] != this.myId) {
      this.remotePid = m['pid'];
      this.connection.setRemoteDescription(new RTCSessionDescription(m));
    }
  } catch(e) {
    console.log("Couldn't understand identity message: " + JSON.stringify(msg) + ": -> " + e.message);
  }
};

PeerConnection_unprivileged.prototype.postMessage = function(ref, continuation) {
  if (!this.connection) {
    return continuation(false);
  }
  // Queue until open.
  if (!this.dataChannel || this.dataChannel.readyState != "open") {
    return this.once('open', this.postMessage.bind(this, ref, continuation));
  }
  window.dc = this.dataChannel;

  console.log("Sending transport data.");
  if(ref['text']) {
    console.log("Sending text: " + ref['text']);
    this.dataChannel.send(JSON.stringify({"text":ref['text']}));
  } else if(ref['binary']) {
    // TODO(willscott): implement direct blob support when available.
    console.log("Transmitting " + ref['binary'].size + " binary bytes");
    var reader = new FileReader();
    reader.addEventListener('load', function(type, ev) {
      var arr = [];
      arr.push.apply(arr, new Uint8Array(ev.target.result));
      // Chunk messages so that packets are below MTU.
      var MAX_LEN = 512;
      var STEP = 300;
      var str = JSON.stringify({"mime": type, "binary": arr});
      var parts = Math.ceil(str.length / MAX_LEN);
      console.log("Sending chunked " + type + " ("+ str.length + " bytes)");
      this.dataChannel.send(JSON.stringify({"binary": parts}));

      var delay = 0;
      var sendPart = function(x) {
        this.dataChannel.send(x);
      };

      while (str.length > 0) {
        setTimeout(sendPart.bind(this, str.substr(0, MAX_LEN)), delay);
        delay += STEP;
        str = str.substr(MAX_LEN);
      }
    }.bind(this, ref['binary'].type), true);

    reader.readAsArrayBuffer(ref['binary']);
  }
  continuation();
};

PeerConnection_unprivileged.prototype.close = function(continuation) {
  delete this.dataChannel;

  if (this.connection) {
    try {
      this.connection.close();
    } catch(e) {
      // Ignore already-closed errors.
    }
    delete this.connection;
  }
  continuation();
};

fdom.apis.register("core.peerconnection", PeerConnection_unprivileged);
<|endoftext|>


<commit_before>/* @flow */
/* eslint-disable */
import type { ThemeType } from '../../types';
import { BORDER_COLOR, BRAND_COLOR } from '../../styles';
import cssEmojis from './cssEmojis';

const defaultTheme = `
html {
  -webkit-user-select: none; /* Safari 3.1+ */
  -moz-user-select: none; /* Firefox 2+ */
  -ms-user-select: none; /* IE 10+ */
  user-select: none; /* Standard syntax */
  -khtml-user-select: none;
  -webkit-touch-callout: none;
}
body {
  font-family: sans-serif;
  line-height: 1.4;
  margin: 0;
  width: 100%;
  max-width: 100%;
  font-size: 15px;
}
a {
  color: #08c;
}
p {
  margin: 0;
}
code {
  font-size: .857em;
  white-space: pre-wrap;
  padding: 0 0.25em;
}
pre {
  padding: 0.5em;
  margin: 0.5em 0;
  font-size: 0.75em;
  white-space: pre;
  overflow-x: auto;
  word-wrap: normal;
}
code, pre {
  border-radius: 3px;
  border: 1px solid rgba(127, 127, 127, 0.25);
  background-color: rgba(127, 127, 127, 0.125);
  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}
table {
  border-collapse: collapse;
  width: 100%;
}
table, th, td {
  border: 1px solid rgba(127, 127, 127, 0.25);
}
thead {
  background: rgba(127, 127, 127, 0.1);
}
th, td {
  align: center;
  padding: 0.25em 0.5em;
}
hr {
  margin: 1em 0;
  border: 0;
  border-top: 1px solid rgba(127, 127, 127, 0.5);
}
.highlight {
  background-color: hsl(51, 94%, 74%);
}
.subheader {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-bottom: 0.25em;
}
.timerow {
  text-align: center;
  color: #999;
  display: flex;
  align-items: center;
  padding: 0.5em 0;
}
.timerow-left,
.timerow-right {
  flex: 1;
  height: 1px;
  margin: 0.5em;
}
.timerow-left {
  background: -webkit-linear-gradient(left, transparent 10%, #999 100%);
}
.timerow-right {
  background: -webkit-linear-gradient(left, #999 0%, transparent 90%);
}
.timestamp {
  color: #999;
  font-size: 14px;
  line-height: 1;
  white-space: nowrap;
}
.message {
  display: flex;
  word-wrap: break-word;
  padding: 1em;
}
.message-brief {
  padding: 0 1em 1em 4em;
}
.avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
}
.avatar img {
  width: 100%;
  border-radius: 3px;
}
.content {
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}
.username {
  font-weight: bold;
  line-height: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.user-group-mention,
.user-mention {
  white-space: nowrap;
  background-color: rgba(0, 0, 0, 0.1);
  border-radius: 3px;
  padding: 0 .2em;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.header-wrapper {
  position: -webkit-sticky;
  position: sticky;
  top: -1px;
  padding: 0.5em;
  z-index: 100;
}
.avatar,
.header-wrapper {
  cursor: pointer;
}
.stream-header {
  padding: 0;
  display: flex;
  flex-direction: row;
}
.stream-text,
.topic-text,
.private-header {
  padding: 0 0.5em;
  line-height: 2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topic-text {
  flex: 1;
  padding-left: 0.5em;
  background: #ccc;
  min-width: 30%;
}
[data-mentioned="true"], [data-wildcard_mentioned="true"] {
  background: rgba(255, 0, 0, 0.05);
}
.message:not([data-read="true"]) {
}
.arrow-right {
  width: 0;
  height: 0;
  border-top: 1em solid transparent;
  border-bottom: 1em solid transparent;
  border-left: 1em solid green;
}
.private-header {
  background: #444;
  color: white;
}
.loading-avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
  border-radius: 4px;
  background: rgba(127, 127, 127, 0.9);
}
.loading-content {
  width: 100%;
}
.loading-subheader {
  display: flex;
  justify-content: space-between;
}
.loading-content .block {
  background: linear-gradient(
    to right,
    rgba(127, 127, 127, 0.5) 0%,
    rgba(127, 127, 127, 0.5) 40%,
    rgba(127, 127, 127, 0.25) 51%,
    rgba(127, 127, 127, 0.5) 60%,
    rgba(127, 127, 127, 0.5) 100%
  );
  background-size: 200% 200%;
	animation: gradient-scroll 1s linear infinite;

  border-radius: 10px;
  height: 8px;
  margin-bottom: 10px;
}
@keyframes gradient-scroll {
	0% { background-position: 100% 50% }
	100% { background-position: 0 50% }
}
.loading-subheader .name {
  width: 10em;
  background-color: rgba(127, 127, 127, 0.9);
}
.loading-subheader .timestamp {
  width: 5em;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.loading-spinner, .outbox-spinner {
  width: 2em;
  height: 2em;
  border-radius: 50%;
  margin: 1em auto;
  font-size: 10px;
  border: 3px solid rgba(82, 194, 175, 0.25);
  border-left: 3px solid rgba(82, 194, 175, 0.75);
  animation: spin 1s linear infinite;
}
.outbox-spinner {
  margin: -16px 0 0 0;
  border-width: 2px;
  width: 12px;
  height: 12px;
  float: right;
}
.message_inline_image {
  text-align: center;
}
.message_inline_image img,
.twitter-image img {
  width: 100%;
  height: 30vh;
  object-fit: contain;
}
blockquote {
  padding-left: 0.5em;
  margin: 0.5em 0 0.5em 0;
  border-left: 3px solid rgba(127, 127, 127, 0.5);
}
ul {
  padding-left: 1em;
  margin: 0.5em 0;
}
.codehilite .gi { color: #00a000; }
.codehilite .gd { color: #a00000; }
.codehilite .k { color: #008000; font-weight: bold; }
.codehilite .kd { color: #008000; font-weight: bold; }
.codehilite .nf { color: #00f; }
.codehilite .s2 { color: #ba2121; }
.codehilite .cp { color: #bc7a00; }
.codehilite .kt { color: #b00040; }
.codehilite .nc { color: #00f; font-weight: bold; }
.codehilite .nb { color: #008000; }
.codehilite .s1 { color: #ba2121; }
.twitter-tweet {
  border: 2px solid rgba(29, 161, 242, 0.5);
  background: rgba(29, 161, 242, 0.1);
  border-radius: 0.25em;
  padding: 0.5em 1em;
  margin: 0.5em 0;
}
.twitter-avatar {
  border-radius: 0.25em;
  margin: 0.4em 0.4em 0.2em 0;
  float: left;
  width: 2.2em;
  height: 2.2em;
}
.twitter-image {
  text-align: center;
  margin: 0.5em auto;
}
.message-tags {
  text-align: right;
  margin: 0.25em 0;
  font-size: 10px;
}
.message-tag {
  padding: 0.25em 0.5em;
  margin-left: 4;
  border-radius: 2px;
  color: rgba(127, 127, 127, 0.75);
  background: rgba(0, 0, 0, 0.1);
}
.reaction-list {
  line-height: 2;
  margin: 0.5em 0;
}
.reaction {
  color: rgba(127, 127, 127, 1);
  padding: 4px;
  border-radius: 4px;
  border: 1px solid rgba(127, 127, 127, 0.75);
  line-height: 1.25em;
  margin-right: 0.5em;
}
.realm-reaction {
  pointer-events: none;
  cursor: default;
  height: "auto";
  width: 1.25em;
  max-height: 1.25em;
  vertical-align: top;
}
.self-voted {
  color: ${BRAND_COLOR};
  border: 1px solid ${BRAND_COLOR};
  background: rgba(36, 202, 194, 0.1);
}
.hidden {
  display: none;
}
.emoji {
  display: inline-block;
  height: 18px;
  width: 18px;
  white-space: nowrap;
  color: transparent;
  vertical-align: text-top;
}
.emoji:before {
  color: white;
}
#typing {
  display: flex;
  padding-left: 1em;
}
#typing .content {
  padding-top: 0.5em;
  padding-bottom: 1em;
}
#typing span {
  display: inline-block;
  background-color: #B6B5BA;
  width: 0.5em;
  height: 0.5em;
  border-radius: 100%;
  margin-right: 5px;
  animation: bob 2s infinite;
}
#typing span:nth-child(2) {
  animation-delay: 0.15s;
}
#typing span:nth-child(3) {
  animation-delay: 0.3s;
  margin-right: 0;
}
@keyframes bob {
  10% {
    transform: translateY(-10px);
    background-color: #9E9DA2;
  }
  50% {
    transform: translateY(0);
    background-color: #B6B5BA;
  }
}
.typing-list {
  background: green;
  height: 20px;
}
#message-loading {
  position: fixed;
  width: 100%;
  opacity: 0.25;
}
#js-error {
  position: fixed;
  width: 100%;
  background: red;
  color: white;
  font-size: 10px;
}
#scroll-bottom {
  position: fixed;
  z-index: 200;
  right: 5px;
  bottom: 15px;
}
#scroll-bottom a {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(82, 194, 175, 0.5);
}
#scroll-bottom svg {
  width: 32px;
  height: 32px;
  fill: rgba(255, 255, 255, 0.75);
}
`;

const darkTheme = `
body {
  color: #d5d9dd;
  background: #212D3B;
}
.topic-text {
  background: #54606E;
}
.highlight {
  background-color: hsla(51, 100%, 64%, 0.42);
}
`;

export default (theme: ThemeType, highlightUnreadMessages: boolean) => `
<style>
${defaultTheme}
${theme === 'night' ? darkTheme : ''}
${cssEmojis}
${highlightUnreadMessages ? '.message:not([data-read="true"]) { background: red; }' : ''}
</style>
`;
<commit_msg>webview: Increase line height for emoji reactions.<commit_after>/* @flow */
/* eslint-disable */
import type { ThemeType } from '../../types';
import { BORDER_COLOR, BRAND_COLOR } from '../../styles';
import cssEmojis from './cssEmojis';

const defaultTheme = `
html {
  -webkit-user-select: none; /* Safari 3.1+ */
  -moz-user-select: none; /* Firefox 2+ */
  -ms-user-select: none; /* IE 10+ */
  user-select: none; /* Standard syntax */
  -khtml-user-select: none;
  -webkit-touch-callout: none;
}
body {
  font-family: sans-serif;
  line-height: 1.4;
  margin: 0;
  width: 100%;
  max-width: 100%;
  font-size: 15px;
}
a {
  color: #08c;
}
p {
  margin: 0;
}
code {
  font-size: .857em;
  white-space: pre-wrap;
  padding: 0 0.25em;
}
pre {
  padding: 0.5em;
  margin: 0.5em 0;
  font-size: 0.75em;
  white-space: pre;
  overflow-x: auto;
  word-wrap: normal;
}
code, pre {
  border-radius: 3px;
  border: 1px solid rgba(127, 127, 127, 0.25);
  background-color: rgba(127, 127, 127, 0.125);
  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}
table {
  border-collapse: collapse;
  width: 100%;
}
table, th, td {
  border: 1px solid rgba(127, 127, 127, 0.25);
}
thead {
  background: rgba(127, 127, 127, 0.1);
}
th, td {
  align: center;
  padding: 0.25em 0.5em;
}
hr {
  margin: 1em 0;
  border: 0;
  border-top: 1px solid rgba(127, 127, 127, 0.5);
}
.highlight {
  background-color: hsl(51, 94%, 74%);
}
.subheader {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-bottom: 0.25em;
}
.timerow {
  text-align: center;
  color: #999;
  display: flex;
  align-items: center;
  padding: 0.5em 0;
}
.timerow-left,
.timerow-right {
  flex: 1;
  height: 1px;
  margin: 0.5em;
}
.timerow-left {
  background: -webkit-linear-gradient(left, transparent 10%, #999 100%);
}
.timerow-right {
  background: -webkit-linear-gradient(left, #999 0%, transparent 90%);
}
.timestamp {
  color: #999;
  font-size: 14px;
  line-height: 1;
  white-space: nowrap;
}
.message {
  display: flex;
  word-wrap: break-word;
  padding: 1em;
}
.message-brief {
  padding: 0 1em 1em 4em;
}
.avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
}
.avatar img {
  width: 100%;
  border-radius: 3px;
}
.content {
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}
.username {
  font-weight: bold;
  line-height: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
.user-group-mention,
.user-mention {
  white-space: nowrap;
  background-color: rgba(0, 0, 0, 0.1);
  border-radius: 3px;
  padding: 0 .2em;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
.header-wrapper {
  position: -webkit-sticky;
  position: sticky;
  top: -1px;
  padding: 0.5em;
  z-index: 100;
}
.avatar,
.header-wrapper {
  cursor: pointer;
}
.stream-header {
  padding: 0;
  display: flex;
  flex-direction: row;
}
.stream-text,
.topic-text,
.private-header {
  padding: 0 0.5em;
  line-height: 2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topic-text {
  flex: 1;
  padding-left: 0.5em;
  background: #ccc;
  min-width: 30%;
}
[data-mentioned="true"], [data-wildcard_mentioned="true"] {
  background: rgba(255, 0, 0, 0.05);
}
.message:not([data-read="true"]) {
}
.arrow-right {
  width: 0;
  height: 0;
  border-top: 1em solid transparent;
  border-bottom: 1em solid transparent;
  border-left: 1em solid green;
}
.private-header {
  background: #444;
  color: white;
}
.loading-avatar {
  min-width: 2em;
  width: 2em;
  height: 2em;
  margin-right: 1em;
  border-radius: 4px;
  background: rgba(127, 127, 127, 0.9);
}
.loading-content {
  width: 100%;
}
.loading-subheader {
  display: flex;
  justify-content: space-between;
}
.loading-content .block {
  background: linear-gradient(
    to right,
    rgba(127, 127, 127, 0.5) 0%,
    rgba(127, 127, 127, 0.5) 40%,
    rgba(127, 127, 127, 0.25) 51%,
    rgba(127, 127, 127, 0.5) 60%,
    rgba(127, 127, 127, 0.5) 100%
  );
  background-size: 200% 200%;
	animation: gradient-scroll 1s linear infinite;

  border-radius: 10px;
  height: 8px;
  margin-bottom: 10px;
}
@keyframes gradient-scroll {
	0% { background-position: 100% 50% }
	100% { background-position: 0 50% }
}
.loading-subheader .name {
  width: 10em;
  background-color: rgba(127, 127, 127, 0.9);
}
.loading-subheader .timestamp {
  width: 5em;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.loading-spinner, .outbox-spinner {
  width: 2em;
  height: 2em;
  border-radius: 50%;
  margin: 1em auto;
  font-size: 10px;
  border: 3px solid rgba(82, 194, 175, 0.25);
  border-left: 3px solid rgba(82, 194, 175, 0.75);
  animation: spin 1s linear infinite;
}
.outbox-spinner {
  margin: -16px 0 0 0;
  border-width: 2px;
  width: 12px;
  height: 12px;
  float: right;
}
.message_inline_image {
  text-align: center;
}
.message_inline_image img,
.twitter-image img {
  width: 100%;
  height: 30vh;
  object-fit: contain;
}
blockquote {
  padding-left: 0.5em;
  margin: 0.5em 0 0.5em 0;
  border-left: 3px solid rgba(127, 127, 127, 0.5);
}
ul {
  padding-left: 1em;
  margin: 0.5em 0;
}
.codehilite .gi { color: #00a000; }
.codehilite .gd { color: #a00000; }
.codehilite .k { color: #008000; font-weight: bold; }
.codehilite .kd { color: #008000; font-weight: bold; }
.codehilite .nf { color: #00f; }
.codehilite .s2 { color: #ba2121; }
.codehilite .cp { color: #bc7a00; }
.codehilite .kt { color: #b00040; }
.codehilite .nc { color: #00f; font-weight: bold; }
.codehilite .nb { color: #008000; }
.codehilite .s1 { color: #ba2121; }
.twitter-tweet {
  border: 2px solid rgba(29, 161, 242, 0.5);
  background: rgba(29, 161, 242, 0.1);
  border-radius: 0.25em;
  padding: 0.5em 1em;
  margin: 0.5em 0;
}
.twitter-avatar {
  border-radius: 0.25em;
  margin: 0.4em 0.4em 0.2em 0;
  float: left;
  width: 2.2em;
  height: 2.2em;
}
.twitter-image {
  text-align: center;
  margin: 0.5em auto;
}
.message-tags {
  text-align: right;
  margin: 0.25em 0;
  font-size: 10px;
}
.message-tag {
  padding: 0.25em 0.5em;
  margin-left: 4;
  border-radius: 2px;
  color: rgba(127, 127, 127, 0.75);
  background: rgba(0, 0, 0, 0.1);
}
.reaction-list {
  line-height: 2;
  margin: 0.5em 0;
}
.reaction {
  color: rgba(127, 127, 127, 1);
  padding: 4px;
  border-radius: 4px;
  border: 1px solid rgba(127, 127, 127, 0.75);
  line-height: 2.5em;
  margin-right: 0.5em;
}
.realm-reaction {
  pointer-events: none;
  cursor: default;
  height: "auto";
  width: 1.25em;
  max-height: 1.25em;
  vertical-align: top;
}
.self-voted {
  color: ${BRAND_COLOR};
  border: 1px solid ${BRAND_COLOR};
  background: rgba(36, 202, 194, 0.1);
}
.hidden {
  display: none;
}
.emoji {
  display: inline-block;
  height: 18px;
  width: 18px;
  white-space: nowrap;
  color: transparent;
  vertical-align: text-top;
}
.emoji:before {
  color: white;
}
#typing {
  display: flex;
  padding-left: 1em;
}
#typing .content {
  padding-top: 0.5em;
  padding-bottom: 1em;
}
#typing span {
  display: inline-block;
  background-color: #B6B5BA;
  width: 0.5em;
  height: 0.5em;
  border-radius: 100%;
  margin-right: 5px;
  animation: bob 2s infinite;
}
#typing span:nth-child(2) {
  animation-delay: 0.15s;
}
#typing span:nth-child(3) {
  animation-delay: 0.3s;
  margin-right: 0;
}
@keyframes bob {
  10% {
    transform: translateY(-10px);
    background-color: #9E9DA2;
  }
  50% {
    transform: translateY(0);
    background-color: #B6B5BA;
  }
}
.typing-list {
  background: green;
  height: 20px;
}
#message-loading {
  position: fixed;
  width: 100%;
  opacity: 0.25;
}
#js-error {
  position: fixed;
  width: 100%;
  background: red;
  color: white;
  font-size: 10px;
}
#scroll-bottom {
  position: fixed;
  z-index: 200;
  right: 5px;
  bottom: 15px;
}
#scroll-bottom a {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(82, 194, 175, 0.5);
}
#scroll-bottom svg {
  width: 32px;
  height: 32px;
  fill: rgba(255, 255, 255, 0.75);
}
`;

const darkTheme = `
body {
  color: #d5d9dd;
  background: #212D3B;
}
.topic-text {
  background: #54606E;
}
.highlight {
  background-color: hsla(51, 100%, 64%, 0.42);
}
`;

export default (theme: ThemeType, highlightUnreadMessages: boolean) => `
<style>
${defaultTheme}
${theme === 'night' ? darkTheme : ''}
${cssEmojis}
${highlightUnreadMessages ? '.message:not([data-read="true"]) { background: red; }' : ''}
</style>
`;
<|endoftext|>


<commit_before>(function (exports) {

	/*
	 * NDDBBrowser
	 *
	 * Sends DATA msgs
	 *
	 */

	exports.NDDBBrowser = NDDBBrowser;

	JSUS = node.JSUS;
	NDDB = node.NDDB;
	TriggerManager = node.TriggerManager;

	NDDBBrowser.id = 'NDDBBrowser';
	NDDBBrowser.name = 'NDDBBrowser';
	NDDBBrowser.version = '0.1';

	NDDBBrowser.dependencies = {
		JSUS: {},
		NDDB: {},
		TriggerManager: {}
	};

	function NDDBBrowser (options) {
		var options = options || {};
		this.options = options;
		this.id = options.id;
		this.nddb = null;
		this.commandsDiv = document.createElement('div');
		this.init(this.options);
	};

	NDDBBrowser.prototype.init = function (options) {

		function addButtons() {
			var id = this.id;
			node.window.addEventButton(id + '_GO_TO_FIRST', '<<', this.commandsDiv, 'go_to_first');
			node.window.addEventButton(id + '_GO_TO_PREVIOUS', '<', this.commandsDiv, 'go_to_previous');
			node.window.addEventButton(id + '_GO_TO_NEXT', '>', this.commandsDiv, 'go_to_next');
			node.window.addEventButton(id + '_GO_TO_LAST', '>>', this.commandsDiv, 'go_to_last');
		};
		addButtons.call(this);
		this.tm = new TriggerManager();
		this.tm.init(options.triggers);
		this.nddb = options.nddb || new NDDB();
	};

	NDDBBrowser.prototype.append = function (root) {
		this.root = root;
		root.appendChild(this.commandsDiv);
		return root;
	};

	NDDBBrowser.prototype.getRoot = function (root) {
		return this.commandsDiv;
	};

	NDDBBrowser.prototype.add = function (o) {
		return this.nddb.insert(o);
	};

	NDDBBrowser.prototype.sort = function (key) {
		return this.nddb.sort(key);
	};

	NDDBBrowser.prototype.addTrigger = function (trigger) {
		return this.tm.addTrigger(trigger);
	};

	NDDBBrowser.prototype.removeTrigger = function (trigger) {
		return this.tm.removeTrigger(trigger);
	};

	NDDBBrowser.prototype.resetTriggers = function () {
		return this.tm.resetTriggers();
	};

	NDDBBrowser.prototype.listeners = function() {
		var that = this;
		var id = this.id;

		node.on(id + '_GO_TO_FIRST', function() {
			var el = that.tm.pullTriggers(that.nddb.first());
			console.log('first');
			console.log(el);
			node.emit(id + '_GOT', el);
		});

		node.on(id + '_GO_TO_PREVIOUS', function() {
			var el = that.tm.pullTriggers(that.nddb.previous());
			console.log('previous');
			console.log(el);
			node.emit(id + '_GOT', el);
		});

		node.on(id + '_GO_TO_NEXT', function() {
			var el = that.tm.pullTriggers(that.nddb.next());
			console.log('next');
			console.log(el);
			node.emit(id + '_GOT', el);
		});

		node.on(id + '_GO_TO_LAST', function() {
			var el = that.tm.pullTriggers(that.nddb.last());
			console.log('last');
			console.log(el);
			node.emit(id + '_GOT', el);
		});
	};

})(node.window.widgets);<commit_msg>NDDB first and last update the reference to the pointer<commit_after>(function (exports) {

	/*
	 * NDDBBrowser
	 *
	 * Sends DATA msgs
	 *
	 */

	exports.NDDBBrowser = NDDBBrowser;

	JSUS = node.JSUS;
	NDDB = node.NDDB;
	TriggerManager = node.TriggerManager;

	NDDBBrowser.id = 'NDDBBrowser';
	NDDBBrowser.name = 'NDDBBrowser';
	NDDBBrowser.version = '0.1';

	NDDBBrowser.dependencies = {
		JSUS: {},
		NDDB: {},
		TriggerManager: {}
	};

	function NDDBBrowser (options) {
		var options = options || {};
		this.options = options;
		this.id = options.id;
		this.nddb = null;
		this.commandsDiv = document.createElement('div');
		this.init(this.options);
	};

	NDDBBrowser.prototype.init = function (options) {

		function addButtons() {
			var id = this.id;
			node.window.addEventButton(id + '_GO_TO_FIRST', '<<', this.commandsDiv, 'go_to_first');
			node.window.addEventButton(id + '_GO_TO_PREVIOUS', '<', this.commandsDiv, 'go_to_previous');
			node.window.addEventButton(id + '_GO_TO_NEXT', '>', this.commandsDiv, 'go_to_next');
			node.window.addEventButton(id + '_GO_TO_LAST', '>>', this.commandsDiv, 'go_to_last');
		};
		addButtons.call(this);
		this.tm = new TriggerManager();
		this.tm.init(options.triggers);
		this.nddb = options.nddb || new NDDB();
	};

	NDDBBrowser.prototype.append = function (root) {
		this.root = root;
		root.appendChild(this.commandsDiv);
		return root;
	};

	NDDBBrowser.prototype.getRoot = function (root) {
		return this.commandsDiv;
	};

	NDDBBrowser.prototype.add = function (o) {
		return this.nddb.insert(o);
	};

	NDDBBrowser.prototype.sort = function (key) {
		return this.nddb.sort(key);
	};

	NDDBBrowser.prototype.addTrigger = function (trigger) {
		return this.tm.addTrigger(trigger);
	};

	NDDBBrowser.prototype.removeTrigger = function (trigger) {
		return this.tm.removeTrigger(trigger);
	};

	NDDBBrowser.prototype.resetTriggers = function () {
		return this.tm.resetTriggers();
	};

	NDDBBrowser.prototype.listeners = function() {
		var that = this;
		var id = this.id;

		node.on(id + '_GO_TO_FIRST', function() {
			var el = that.tm.pullTriggers(that.nddb.first());
			if (el) node.emit(id + '_GOT', el);
			console.log('first');
			console.log(el);
		});

		node.on(id + '_GO_TO_PREVIOUS', function() {
			var el = that.tm.pullTriggers(that.nddb.previous());
			if (el) node.emit(id + '_GOT', el);
			console.log('previous');
			console.log(el);

		});

		node.on(id + '_GO_TO_NEXT', function() {
			var el = that.tm.pullTriggers(that.nddb.next());
			if (el) node.emit(id + '_GOT', el);
			console.log('next');
			console.log(el);
		});

		node.on(id + '_GO_TO_LAST', function() {
			var el = that.tm.pullTriggers(that.nddb.last());
			if (el) node.emit(id + '_GOT', el);
			console.log('last');
			console.log(el);
		});
	};

})(node.window.widgets);<|endoftext|>