<commit_before>cdb.admin.overlays.Annotation = cdb.geo.ui.Annotation.extend({

  className: "annotation overlay",

  template_name: 'table/views/overlays/annotation',

  events: {

    "mouseenter .text":   "_onMouseEnter",
    "mouseup":            "_onMouseUp",

    "click .close":      "_close",
    "click .content":    "_onClickEdit",
    "click .text":       "_onClickEdit",
    "dblclick .content": "_onDblClick",
    "dblclick .text":    "_onDblClick",

    "keyup .text":       "_onKeyUp",
    "paste .text":       "_onPaste"

  },

  initialize: function() {

    _.bindAll(this, "_close", "_onChangeMode", "_onKeyDown");

    this.vis    = this.options.vis;
    this.canvas = this.options.canvas;

    this.mapView = this.options.mapView;

    this._bindMap();

    this.template = this.getTemplate(this.template_name);

    this._setupModels();

    // zoom level config
    var minZoomLevel     = this.mapView.map.get("minZoom");
    var maxZoomLevel     = this.mapView.map.get("maxZoom");

    this.form_data = [{
        name: 'Text',
        form: {
          'font-size':  { type: 'simple_number', value: 12, min: 5, max: 50, inc: 2, disable_triggering: true },
          'color':      { type: 'color', value: '#FFF', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
          'font-family-name': {
            type: 'select',
            value: "Helvetica",
            extra: ["Helvetica", "Droid Sans", "Vollkorn", "Roboto", "Open Sans", "Lato", "Graduate", "Gravitas One", "Old Standard TT"]
          },
          'text-align':      { type: 'text_align', value: 'left', alignments: { left: true, right: true, center: false } },
        }
      }, {
        name: 'Box',
        form: {
          'box-color':  { type: 'color', value: '#000', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
          'box-opacity':  { type: 'simple_opacity', value: .7, min:0, max:1, inc: .1, disable_triggering: true },
          'box-padding':  { type: 'simple_number_with_label', value: 5, min: 5, max: 50, inc: 1, label: "P", disable_triggering: true }
        }
      }, {
        name: 'Line',
        form: {
          'line-color':  { type: 'color', value: '#000', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
          'line-width':  { type: 'simple_number_with_label', value: 50, min: 5, max: 100, inc: 1, label: 'W', disable_triggering: true },
        }
      } , {
        name: 'Zoom (min-max)',
        form: {
          'min-zoom':  { type: 'simple_number', value: minZoomLevel, min: minZoomLevel, max: maxZoomLevel, inc: 1, classes: "margin-min", disable_triggering: true },
          'max-zoom':  { type: 'simple_number_with_label', value: maxZoomLevel, min: minZoomLevel, max: maxZoomLevel, inc: 1, label: '↔', disable_triggering: true },
        }
      }];
  },

  _bindMap: function() {

    this.mapView.map.bind('change',      this._place, this);
    this.mapView.map.bind('change:zoom', this._applyZoomLevelStyle, this);
    this.mapView.bind('zoomstart', this._hideOverlay, this);
    this.mapView.bind('zoomend',   this._showOverlay, this);

  },

  _unbindMap: function() {

    this.mapView.map.unbind('change',      this._place, this);
    this.mapView.map.unbind('change:zoom', this._applyZoomLevelStyle, this);
    this.mapView.unbind('zoomstart', this._hideOverlay, this);
    this.mapView.unbind('zoomend',   this._showOverlay, this);

  },

  // Setup the internal and custom model
  _setupModels: function() {

    var self  = this;
    var extra = this.extra = this.model.get("extra");

    this.model.set({ text: extra.text }, { silent: true });

    var applyStyle = function() {
      self._applyStyle();
      self.model.save();
    };

    // Binding
    this.model.bind('remove',          this.hide,             this);

    this.model.bind('change:style',    applyStyle,            this);
    this.model.bind('change:text',     this._setText,         this);
    this.model.bind('change:display',  this._onChangeDisplay, this);
    this.model.bind('change:extra',    this._onChangeExtra,   this);
    this.model.bind('change:selected', this._onChangeSelected, this);

    // Internal model to store the editing state
    this.editModel = new cdb.core.Model({ mode: "" });
    this.editModel.bind('change:mode', this._onChangeMode, this);

    this.add_related_model(this.editModel);

  },

  // Element events 
  _onKeyUp: function(e) {

    if (this.timeout) {
      clearTimeout(this.timeout);
    }

    this.model.set({ text: this.$text.text() }, { silent: true });

  },

  _onClickEdit: function(e) {

    this.killEvent(e);

    $(document).bind('keydown', this._onKeyDown);

    this.trigger("clickEdit", this.model, this.form_data);
    this.model.set("selected", true);

  },

  _onKeyDown: function(e) {

    var selected = this.model.get("selected");

    if (selected) {

      var editable = this.editModel.get("mode") !== "editable";
      var focus    = this.$(".overlay_text").is(":focus");

      // hitting the backspace removes the overlay
      if (e.keyCode === 8 && (editable || !focus)) {
        this.killEvent(e);
        this._close();
      }
    }

    if (e.keyCode === 27) {
      this.editModel.set("mode", "");
    }

  },

  _onPaste: function(e) {

    var self = this;

    setTimeout(function() {

      var text = cdb.Utils.stripHTML(self.model.get("text"));
      self.model.set("text", text)

    }, 200);

  },

  _onDblClick: function(e) {

    this.killEvent(e);
    this.editModel.set("mode", "editable");
  },

  _onMouseUp: function() {

    var editable = this.editModel.get("mode") == "editable";

    if (!editable) {
      this._savePosition();
    }

  },

  _savePosition: function() {

    var extra = this.model.get("extra");

    var x     = this.model.get("x");
    var y     = this.model.get("y");

    var oldX   = this.$el.position().left;
    var oldY   = this.$el.position().top;
    var height = this.$el.height();

    if (x == oldX && y == oldY) return;

    var x = this.$el.position().left;
    var y = this.$el.position().top;

    var style      = this.model.get("style");

    var lineWidth  = style["line-width"];
    var textAlign  = style["text-align"];

    y = y + Math.ceil(this.$el.outerHeight(true)/2);

    if (textAlign === "right") {
      x = x + this.$el.width() + lineWidth + this.$(".ball").width();
    } else {
      x = x - lineWidth;
    }

    var latlng = this.mapView.pixelToLatLon([x , y]);
    extra.latlng = [latlng.lat, latlng.lng];

    if (this.model.id) { // avoid saving if the model was removed<commit_msg>Uses isNew method instead of relying on the id<commit_after>cdb.admin.overlays.Annotation = cdb.geo.ui.Annotation.extend({

  className: "annotation overlay",

  template_name: 'table/views/overlays/annotation',

  events: {

    "mouseenter .text":   "_onMouseEnter",
    "mouseup":            "_onMouseUp",

    "click .close":      "_close",
    "click .content":    "_onClickEdit",
    "click .text":       "_onClickEdit",
    "dblclick .content": "_onDblClick",
    "dblclick .text":    "_onDblClick",

    "keyup .text":       "_onKeyUp",
    "paste .text":       "_onPaste"

  },

  initialize: function() {

    _.bindAll(this, "_close", "_onChangeMode", "_onKeyDown");

    this.vis    = this.options.vis;
    this.canvas = this.options.canvas;

    this.mapView = this.options.mapView;

    this._bindMap();

    this.template = this.getTemplate(this.template_name);

    this._setupModels();

    // zoom level config
    var minZoomLevel     = this.mapView.map.get("minZoom");
    var maxZoomLevel     = this.mapView.map.get("maxZoom");

    this.form_data = [{
        name: 'Text',
        form: {
          'font-size':  { type: 'simple_number', value: 12, min: 5, max: 50, inc: 2, disable_triggering: true },
          'color':      { type: 'color', value: '#FFF', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
          'font-family-name': {
            type: 'select',
            value: "Helvetica",
            extra: ["Helvetica", "Droid Sans", "Vollkorn", "Roboto", "Open Sans", "Lato", "Graduate", "Gravitas One", "Old Standard TT"]
          },
          'text-align':      { type: 'text_align', value: 'left', alignments: { left: true, right: true, center: false } },
        }
      }, {
        name: 'Box',
        form: {
          'box-color':  { type: 'color', value: '#000', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
          'box-opacity':  { type: 'simple_opacity', value: .7, min:0, max:1, inc: .1, disable_triggering: true },
          'box-padding':  { type: 'simple_number_with_label', value: 5, min: 5, max: 50, inc: 1, label: "P", disable_triggering: true }
        }
      }, {
        name: 'Line',
        form: {
          'line-color':  { type: 'color', value: '#000', extra: { tick: "left", picker_horizontal_position: "left", picker_vertical_position: "down" }},
          'line-width':  { type: 'simple_number_with_label', value: 50, min: 5, max: 100, inc: 1, label: 'W', disable_triggering: true },
        }
      } , {
        name: 'Zoom (min-max)',
        form: {
          'min-zoom':  { type: 'simple_number', value: minZoomLevel, min: minZoomLevel, max: maxZoomLevel, inc: 1, classes: "margin-min", disable_triggering: true },
          'max-zoom':  { type: 'simple_number_with_label', value: maxZoomLevel, min: minZoomLevel, max: maxZoomLevel, inc: 1, label: '↔', disable_triggering: true },
        }
      }];
  },

  _bindMap: function() {

    this.mapView.map.bind('change',      this._place, this);
    this.mapView.map.bind('change:zoom', this._applyZoomLevelStyle, this);
    this.mapView.bind('zoomstart', this._hideOverlay, this);
    this.mapView.bind('zoomend',   this._showOverlay, this);

  },

  _unbindMap: function() {

    this.mapView.map.unbind('change',      this._place, this);
    this.mapView.map.unbind('change:zoom', this._applyZoomLevelStyle, this);
    this.mapView.unbind('zoomstart', this._hideOverlay, this);
    this.mapView.unbind('zoomend',   this._showOverlay, this);

  },

  // Setup the internal and custom model
  _setupModels: function() {

    var self  = this;
    var extra = this.extra = this.model.get("extra");

    this.model.set({ text: extra.text }, { silent: true });

    var applyStyle = function() {
      self._applyStyle();
      self.model.save();
    };

    // Binding
    this.model.bind('remove',          this.hide,             this);

    this.model.bind('change:style',    applyStyle,            this);
    this.model.bind('change:text',     this._setText,         this);
    this.model.bind('change:display',  this._onChangeDisplay, this);
    this.model.bind('change:extra',    this._onChangeExtra,   this);
    this.model.bind('change:selected', this._onChangeSelected, this);

    // Internal model to store the editing state
    this.editModel = new cdb.core.Model({ mode: "" });
    this.editModel.bind('change:mode', this._onChangeMode, this);

    this.add_related_model(this.editModel);

  },

  // Element events 
  _onKeyUp: function(e) {

    if (this.timeout) {
      clearTimeout(this.timeout);
    }

    this.model.set({ text: this.$text.text() }, { silent: true });

  },

  _onClickEdit: function(e) {

    this.killEvent(e);

    $(document).bind('keydown', this._onKeyDown);

    this.trigger("clickEdit", this.model, this.form_data);
    this.model.set("selected", true);

  },

  _onKeyDown: function(e) {

    var selected = this.model.get("selected");

    if (selected) {

      var editable = this.editModel.get("mode") !== "editable";
      var focus    = this.$(".overlay_text").is(":focus");

      // hitting the backspace removes the overlay
      if (e.keyCode === 8 && (editable || !focus)) {
        this.killEvent(e);
        this._close();
      }
    }

    if (e.keyCode === 27) {
      this.editModel.set("mode", "");
    }

  },

  _onPaste: function(e) {

    var self = this;

    setTimeout(function() {

      var text = cdb.Utils.stripHTML(self.model.get("text"));
      self.model.set("text", text)

    }, 200);

  },

  _onDblClick: function(e) {

    this.killEvent(e);
    this.editModel.set("mode", "editable");
  },

  _onMouseUp: function() {

    var editable = this.editModel.get("mode") == "editable";

    if (!editable) {
      this._savePosition();
    }

  },

  _savePosition: function() {

    var extra = this.model.get("extra");

    var x     = this.model.get("x");
    var y     = this.model.get("y");

    var oldX   = this.$el.position().left;
    var oldY   = this.$el.position().top;
    var height = this.$el.height();

    if (x == oldX && y == oldY) return;

    var x = this.$el.position().left;
    var y = this.$el.position().top;

    var style      = this.model.get("style");

    var lineWidth  = style["line-width"];
    var textAlign  = style["text-align"];

    y = y + Math.ceil(this.$el.outerHeight(true)/2);

    if (textAlign === "right") {
      x = x + this.$el.width() + lineWidth + this.$(".ball").width();
    } else {
      x = x - lineWidth;
    }

    var latlng = this.mapView.pixelToLatLon([x , y]);
    extra.latlng = [latlng.lat, latlng.lng];

    if (!this.model.isNew()) { // avoid saving it if the model was removed
{'fraction_non_alphanumeric': 0.172072608242821, 'fraction_numerical': 0.005505133164707633, 'mean_word_length': 2.680996714129244, 'pattern_counts': {'":': 20, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3106706', 'n_tokens_mistral': 4610, 'n_tokens_neox': 4398, 'n_words': 934}