cartodb-4.42/lib/assets/javascripts/cartodb/table/overlays/annotation.js
2024-04-06 05:25:13 +00:00

669 lines
16 KiB
JavaScript

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.html() }, { 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.target).hasClass("overlay_text") || $(e.target).hasClass("cartodb-map")) {
if (e.keyCode === $.ui.keyCode.BACKSPACE && (editable || !focus)) {
this.killEvent(e);
this._close();
}
}
if (editable && !focus) {
if (e.which == 67 && (e.ctrlKey || e.metaKey)) {
this.trigger('duplicate', this.model, this);
}
}
}
if (e.keyCode === $.ui.keyCode.ESCAPE) {
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
this.model.save({ extra: extra });
}
},
_onMouseDown: function() {},
_onMouseEnter: function() {
this.$el.addClass("hover");
if (this.editModel.get("mode") === "editable") {
if (this.timeout) clearTimeout(this.timeout);
}
},
_onMouseLeave: function() {
this.$el.removeClass("hover");
var self = this;
if (this.editModel.get("mode") === "editable") {
this.timeout = setTimeout(function() {
self.editModel.set("mode", "");
}, 250);
}
},
_hideOverlay: function() {
this.$el.fadeOut(150);
},
_showOverlay: function() {
if (!this._belongsToCanvas()) return;
var self = this;
this.$el.stop().delay(500).fadeIn(150, function() {
self.$el.css({ display: "inline-table" }); // trick so we don't need to set the width
});
},
// canonical show method for the overlay
show: function(animated) {
if (!this._belongsToCanvas()) return;
this.$el.show();
this.$el.css({ display: "inline-table "});
if (true) this.$el.addClass('animated bounceIn');
var self = this;
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
self.$el.removeClass("animated bounceIn");
});
},
// canonical hide (= destroy) method for the overlay
hide: function(callback) {
var self = this;
this.unbind("mouseenter", this._onMouseEnter, this);
this.unbind("mouseup", this._onMouseUp, this);
$(document).unbind('keydown', this._onKeyDown, this);
this.$el
.removeClass('animated bounceIn')
.addClass('animated bounceOut')
callback && _.isFunction(callback) && callback();
this._unbindMap();
cdb.god.unbind("closeDialogs", this._onCloseDialogs, this);
// Give it some time to complete the animation
setTimeout(function() {
self.clean();
}, 550);
},
_close: function(e) {
this.killEvent(e);
var self = this;
this.hide(function() {
self.trigger("remove", self);
});
},
_place: function() {
var mode = this.editModel.get("mode");
if (mode === "editable") return;
var style = this.model.get("style");
var lineWidth = style["line-width"];
var textAlign = style["text-align"];
var pos = this.mapView.latLonToPixel(this.model.get('extra').latlng);
var size = this.mapView.getSize();
var top = pos.y - Math.ceil(this.$el.outerHeight(true)/2);
var left = pos.x + lineWidth;
if (textAlign === "right") {
left = pos.x - this.$el.width() - lineWidth - this.$(".ball").width();
}
this.$el.css({ top: top, left: left });
},
_belongsToCanvas: function() {
return this.model.get("device") === this.canvas.get("mode");
},
_onChangeDisplay: function() {
var display = this.model.get("display");
if (display && this._belongsToCanvas()) {
this.show();
} else {
this._hideOverlay();
}
},
_onChangeExtra: function() {
var extra = this.model.get("extra");
extra.text = this.model.get("text");
this.model.set({ extra: extra }, { silent: true });
},
_getStyleProperty: function(property) {
var style = this.model.get("style");
return style[property];
},
/*
* Applies style to the content of the widget
*/
_applyStyle: function() {
var style = this.model.get("style");
var textAlign = style["text-align"];
var boxColor = style["box-color"];
var boxOpacity = style["box-opacity"];
var boxPadding = style["box-padding"];
var lineWidth = style["line-width"];
var lineColor = style["line-color"];
var fontFamily = style["font-family-name"];
if (boxOpacity === 0) {
this.$el.addClass("border-dark");
} else {
this.$el.removeClass("border-dark");
}
if (boxColor === "#FFFFFF") {
this.$el.addClass("white-box");
} else {
this.$el.removeClass("white-box");
}
this.$text.css(style);
this.$(".content").css("padding", boxPadding);
this.$text.css("font-size", style["font-size"] + "px");
this.$el.css("z-index", style["z-index"]);
this.$(".stick").css({ width: lineWidth, left: -lineWidth });
var fontFamilyClass = "";
if (fontFamily == "Droid Sans") fontFamilyClass = "droid";
else if (fontFamily == "Vollkorn") fontFamilyClass = "vollkorn";
else if (fontFamily == "Open Sans") fontFamilyClass = "open_sans";
else if (fontFamily == "Roboto") fontFamilyClass = "roboto";
else if (fontFamily == "Lato") fontFamilyClass = "lato";
else if (fontFamily == "Graduate") fontFamilyClass = "graduate";
else if (fontFamily == "Gravitas One") fontFamilyClass = "gravitas_one";
else if (fontFamily == "Old Standard TT") fontFamilyClass = "old_standard_tt";
this.$el
.removeClass("droid")
.removeClass("vollkorn")
.removeClass("roboto")
.removeClass("open_sans")
.removeClass("lato")
.removeClass("graduate")
.removeClass("gravitas_one")
.removeClass("old_standard_tt");
this.$el.addClass(fontFamilyClass);
if (textAlign === "right") {
this.$el.addClass("align-right");
this.$(".stick").css({ left: "auto", right: -lineWidth });
} else {
this.$el.removeClass("align-right");
}
this._place();
this._applyZoomLevelStyle();
},
_applyZoomLevelStyle: function() {
var style = this.model.get("style");
var extra = this.model.get("extra");
var boxColor = style["box-color"];
var boxOpacity = style["box-opacity"];
var lineColor = style["line-color"];
var minZoom = style["min-zoom"];
var maxZoom = style["max-zoom"];
var currentZoom = this.mapView.map.get("zoom");
var textOpacity = 1;
if (currentZoom >= minZoom && currentZoom <= maxZoom) {
textOpacity = 1;
var rgbaLineCol = this._getRGBA(lineColor, 1);
var rgbaBoxCol = this._getRGBA(boxColor, boxOpacity);
} else {
textOpacity = .5;
var rgbaLineCol = this._getRGBA(lineColor, .2);
var rgbaBoxCol = this._getRGBA(boxColor, .2);
}
this.$(".text").animate({ opacity: textOpacity }, 150);
this.$el.css("background-color", rgbaBoxCol);
this.$(".stick").css("background-color", rgbaLineCol);
this.$(".ball").css("background-color", rgbaLineCol);
},
_onChangeSelected: function() {
var selected = this.model.get("selected");
if (selected) {
this.$el.addClass("selected");
if (this._getStyleProperty("box-opacity") === 0) {
this.$el.addClass("border-dark");
}
if (this._getStyleProperty("box-color") === "#FFFFFF") {
this.$el.addClass("white-box");
}
} else {
this.$el
.removeClass("selected")
.removeClass("border-dark")
.removeClass("white-box");
this._disableEditingMode();
}
},
_onChangeMode: function() {
var mode = this.editModel.get("mode");
this.trigger('editing', mode === 'editable', this);
if (mode == "editable") {
this._enableEditingMode();
} else {
this._disableEditingMode();
}
},
_enableEditingMode: function() {
this.$el
.addClass("editable")
.addClass("disabled");
this.$text.attr("contenteditable", true).focus();
var style = this.model.get("style");
var width = style["box-width"];
var text = this.model.get("text");
this.$el.css("width", "auto");
this.$el.css("max-width", width);
this.$text.html(text);
this.$(".hint").fadeIn(150);
},
_disableEditingMode: function() {
$(document).unbind('keydown', this._onKeyDown);
var text = this._transformToMarkdown(this.model.get("text"));
this.editModel.set("mode", "");
if (text) {
var self = this;
self.$(".hint").fadeOut(150, function() {
self.$el
.removeClass("editable")
.removeClass("disabled");
self.$text.attr("contenteditable", false);
});
self.$text.html(text);
self._savePosition();
} else {
this._close();
}
},
_setText: function() {
var text = this.model.get("text");
var rendered_text = this._transformToMarkdown(text);
var extra = this.model.get("extra");
extra.text = text;
extra.rendered_text = rendered_text
this.model.save({ extra: extra });
if (rendered_text) this.$text.html(rendered_text);
},
_transformToMarkdown: function(text) {
text = markdown.toHTML(text)
text = text.replace(/&lt;/g, "<");
text = text.replace(/&gt;/g, ">");
text = text.replace(/<p>/g, "");
text = text.replace(/&amp;nbsp;/g, " ");
text = text.replace(/<\/p>/g, "");
return text;
},
_onCloseDialogs: function() {
if (this.model.get("selected") !== undefined) this.model.set("selected", false);
},
_setupText: function() {
this.$text = this.$(".content div.text");
this.$text.html(this._transformToMarkdown(this.model.get("text")));
},
render: function() {
this.$el.append(this.template(this.model.attributes));
this.$el.addClass(this.model.get("device"));
this._setupText();
var self = this;
setTimeout(function() {
self._applyStyle();
self._place();
self.show();
}, 500);
cdb.god.unbind("closeDialogs", this._onCloseDialogs, this);
cdb.god.bind("closeDialogs", this._onCloseDialogs, this);
return this;
}
});