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

640 lines
18 KiB
JavaScript

/**
* Actions menu view, aka LayersPanel
*
* - It needs at least visualization and user models.
* Globalerror to show connection or fetching errors.
*
* var menu = new cdb.admin.LayersPanel({
* vis: vis_model,
* user: user_model,
* globalError: globalError_obj
* });
*/
cdb.admin.LayersPanel = cdb.ui.common.TabPane.extend({
className: 'table_panel',
animation_time: 300,
_TEXTS: {
sort: {
torque: _t('Animated layers must be on top of other layers.'),
tiled: _t('Tiled layers can\'t be above animated layers.')
},
raster: _t("You can't add a raster layer into a visualization"),
error: {
default: _t('Something went wrong, try again later')
}
},
events: {
'click .add_layer': '_addLayerDialog'
},
initialize: function() {
this.layer_panels = []; // Layer panels added in the current visualization
this.vis = this.options.vis;
this.master_vis = this.options.master_vis;
this.map = this.vis.map;
this.user = this.options.user;
this.globalError = this.options.globalError;
// View bindings
_.bindAll(this, '_sortStart', '_sortChange', '_sortBeforeStop', '_sortLayers',
'_manageLayers', '_checkResize', '_moveSortTooltip');
this.map.layers.bind('add', this._newLayer, this);
this.map.layers.bind('reset', this._resetLayers, this);
this.map.layers.bind('add remove', this._updateLayerPositionLabel, this);
$(window).bind('resize', this._checkResize);
this.add_related_model(this.map.layers);
this.add_related_model(this.map);
this._bindSort(); // Bind sort movement
// the following call is not needed, 'reset' on map.layers will be raised
// after this is loaded. If the map data was not loaded using AJAX this would
// need to be enabled
//this._addLayers(this.map.layers);
// Setting several view parameters
this.model = new cdb.core.Model({
open: true,
activeWorkView: 'table'
});
// Bind model changes
this.model.bind('change:open', this._setPanelState, this);
// Visualization changes
this.vis.bind('change:type', this.hide, this);
this.add_related_model(this.vis);
cdb.ui.common.TabPane.prototype.initialize.call(this);
},
// Setup panel depending view model state
_setPanelState: function() {
// If it is open, add open class
this.$el[ this.model.get('open') ? 'addClass' : 'removeClass' ]('opened');
},
/* Layer panel functions */
_resetLayers: function() {
//clean all views
this.removeTabs();
// Empty layers array
this.layer_panels = [];
// Add again the layers
this._addLayers(this.map.layers);
},
_addLayers: function(layers) {
// Add 'add layer' button first
this._addLayerAddButton();
// Add layers
var self = this;
layers.each(function(l, pos) {
self._addLayer(l, pos);
});
// Set default layer
this._setDefaultLayer();
},
_newLayer: function(layer) {
this._addLayer(layer);
},
_addLayer: function(layer, pos) {
var self = this;
if (layer.get('type') == 'CartoDB' ||
layer.get('type') == 'torque') {
var v = new cdb.admin.LayerPanelView({
model: layer,
vis: self.vis,
user: self.user,
globalError: self.options.globalError
});
v.bind('toggle', self._toggle, self);
v.bind('switchTo', self._switchTo, self);
v.bind('delete', self._openDeleteLayerDialog, self);
v.bind('show', self.show, self);
v.bind('destroy', self._removeLayer, self);
v.bind('addColumn', self._addColumn, self);
self.addTab(layer.cid, v, { after: 0 });
v.setActiveWorkView(self.model.get('activeWorkView'));
self.layer_panels.push(v);
self._bindLayerTooltip(v);
// no pos -> new layer -> set as current
if (!pos) {
self._switchTo(v, true);
v.setPanelStatus();
}
self._checkLayers();
}
},
_addColumn: function() {
table.tableTab.tableView.addColumn();
},
_removeLayer: function(dataLayerCid) {
var layer_view = this.getPane(dataLayerCid);
var self = this;
this.layer_panels = _.filter(this.layer_panels, function(view) {
if (view != layer_view) return view;
});
var tabBind = function(dataLayerCid) {
var view = self.getPane(dataLayerCid);
self.trigger('switch', view);
self.vis.save('active_layer_id', view.dataLayer.id);
self.show(view.panels.activeTab);
self.unbind('tabEnabled', tabBind);
}
this.bind('tabEnabled', tabBind);
this._unbindLayerTooltip(layer_view);
this.removeTab(dataLayerCid);
this._checkLayers();
this._manageLayers();
if (self.map.layers.getDataLayers().length === 1) {
_.last(self.map.layers.getDataLayers()).set('visible', true);
}
},
_setDefaultLayer: function() {
var layer_id = this.vis.get('active_layer_id');
var layer = _.last(this.layer_panels);
// Get layer from layer_id
if (layer_id) {
for (var i in this.layer_panels) {
var lyr = this.layer_panels[i];
if (lyr.dataLayer.id == layer_id) {
layer = lyr;
break;
}
}
}
// Any layer? Switch to it
if (layer) {
this._switchTo(layer, false);
}
},
// Check how many layers-view there are in the panel
_checkLayers: function() {
var cartodbLayers = this.map.layers.getDataLayers().length;
if (cartodbLayers === 1) {
this._unbindSort();
} else {
this._bindSort();
}
},
/* Add layer functions */
_addLayerAddButton: function() {
this.$('.add_layer').remove();
var template = this.getTemplate('table/views/add_layer');
this.$el.prepend(template());
},
_addLayerDialog: function(e) {
this.killEvent(e);
if (!this.user.canAddLayerTo(this.map)) {
var dlg = new cdb.editor.LimitsReachView({
clean_on_hide: true,
enter_to_confirm: true,
user: this.user
});
dlg.appendToBody();
return;
}
if (this.vis.isVisualization()) {
this._createAddLayerDialog();
} else {
this.createVisDlg = new cdb.editor.CreateVisFirstView({
clean_on_hide: true,
enter_to_confirm: true,
model: this.master_vis,
router: window.table_router,
title: 'A map is required to add layers',
explanation: 'A map is a shareable mix of layers, styles and queries. You can view all your maps in your dashboard.',
success: this._createAddLayerDialog.bind(this)
});
this.createVisDlg.appendToBody();
}
},
_createAddLayerDialog: function() {
var model = new cdb.editor.AddLayerModel({}, {
vis: this.vis,
map: this.map,
user: this.user
});
var dialog = new cdb.editor.AddLayerView({
model: model,
user: this.user
});
dialog.appendToBody();
},
_bindLayerTooltip: function(v) {
var self = this;
this._unbindLayerTooltip(v);
v && v.$('.info').tipsy({
live: true,
gravity: 'e',
offset: 0,
fade: true,
title: function() {
if (!self.vis.isVisualization() || self.model.get('open')) {
return '';
}
return $(this).find('span.name').text();
}
});
},
_unbindLayerTooltip: function(v) {
v && v.$(".info").unbind('mouseenter mouseleave');
if (v && v.$(".info").data('tipsy')) v.$(".info").data('tipsy').remove();
},
_hideLayerTooltip: function(v) {
v && v.$(".info").tipsy("hide")
},
////////////////////
// Sort functions //
////////////////////
_unbindSort: function() {
this.$el.sortable('destroy')
},
_bindSort: function() {
this._unbindSort();
this.$el.sortable({
axis: "y",
items: "> .layer_panel",
handle: ".layer-info .info",
cancel: '',
contaiment: "parent",
opacity: 0.5,
start: this._sortStart,
change: this._sortChange,
beforeStop: this._sortBeforeStop,
update: this._sortLayers,
forcePlaceholderSize: false
}).disableSelection();
},
// Before start sorting action
_sortStart: function(e, ui) {
this.sort_actions = {
original_layer_pos: $(ui.item).index(),
$layer: $(ui.item)
}
var active_height = $(ui.item).outerHeight();
$(ui.placeholder).outerHeight(active_height);
},
// When any change happens sorting
// - If a torque layer tries to move to other position, sorting is cancelled
// and a yellow tooltip should appear.
// - If a cartodb layer tries to move to other position and there is a torque
// layer in the visualization, sorting is cancelled and a yellow tooltip
// should appear.
_sortChange: function(e, ui) {
var isAnyTorque = _.contains(this.map.layers.pluck('type'), 'torque');
var isLayerTorque = $(ui.item).attr('layer-type') == "torque";
var active_height = $(ui.item).outerHeight();
// Torque
if (isAnyTorque) {
// Torque layer
if (isLayerTorque) {
if (ui.placeholder.index() == 1) {
this.cancelSort = false;
this._removeSortTooltip();
} else {
this.cancelSort = true;
if (!this.sortTooltip) this._addSortTooltip(e, 'torque');
}
this.$('section.add_layer').after(
$(ui.placeholder)
.outerHeight(active_height)
.css('display','block')
)
} else {
if (ui.placeholder.index() != 1) {
this.cancelSort = false;
$(ui.placeholder)
.outerHeight(active_height)
.css('display','block');
this._removeSortTooltip();
} else {
this.cancelSort = true;
this.$('section.layer_panel:eq(' + this.sort_actions.original_layer_pos + ')').before(
$(ui.placeholder)
.outerHeight(active_height)
.css('display','block')
)
if (!this.sortTooltip) this._addSortTooltip(e, 'tiled');
}
}
} else {
this.cancelSort = false;
$(ui.placeholder)
.outerHeight(active_height)
.css('display','block');
}
},
// If it is necessary to cancel de sorting
_sortBeforeStop: function() {
this._removeSortTooltip();
if (this.cancelSort) this.$el.sortable('cancel');
this.cancelSort = false;
},
_addSortTooltip: function(e, type) {
this.sortTooltip = new cdb.admin.TooltipTrails({
className: 'tooltip-trails',
msg: this._TEXTS.sort[type]
});
this.sort_actions.$layer.bind('mousemove', this._moveSortTooltip);
this.$el.append(this.sortTooltip.render().el);
this.sortTooltip.show({
left: e.pageX - this.$el.offset().left,
top: e.pageY - this.$el.offset().top
});
this.addView(this.sortTooltip);
},
_moveSortTooltip: function(e) {
// Get correct first drag coordinates
var parentOffset = this.$el.offset();
var position = {
left: (e.pageX - parentOffset.left),
top: (e.pageY - parentOffset.top)
};
this.sortTooltip.move(position);
},
_removeSortTooltip: function() {
this.sort_actions.$layer.unbind('mousemove', this._moveSortTooltip);
if (this.sortTooltip) {
this.sortTooltip.clean();
this.removeView(this.sortTooltip);
delete this.sortTooltip;
}
},
_sortLayers: function(event, ui) {
var self = this;
// New index of the layer in the collection of layers, assuming there
// is a base layer at position 0
var newIndex = $('.layer_panel').length - $('.layer_panel').index(ui.item);
var layerId = ui.item.attr('model-id');
var layer = this.map.layers.get(layerId);
// layers don't need to be reset when they are sorted
// and save the new layers order to the server
this.map.layers.unbind('reset', this._resetLayers, this);
this.map.layers.moveLayer(layer, {
to: newIndex,
complete: function() {
self._updateLayerPositionLabel();
self.map.layers.bind('reset', self._resetLayers, self);
}
});
},
_updateLayerPositionLabel: function() {
_.each(this.layer_panels, function(layer) {
layer.setLayerOrder(layer.model);
});
},
/* Magic layers */
_manageLayers: function() {
var cartodbLayers = this.map.layers.getDataLayers().length;
// If there isn't any layer added
if (cartodbLayers === 0) {
return false;
}
var $active_layer = this.activePane.$el;
// Move rest of panels
this.$('.layer_panel').removeClass('active');
// Add active class
$active_layer.addClass('active');
// Set rest of layer_panels to height 60px
// Decide later about compress them
// TODO: not use jquery
this.$('.layer_panel').not('.layer_panel.active').css({ height: 66 });
// Get previous siblings and calculate space
// TODO: not use jquery
var pre_size = $active_layer.prevAll('.layer_panel').size();
var t_s = (pre_size == 0) ? 0 : (pre_size * 43);
// Get next siblings and calculate space
// TODO: not use jquery
var next_size = $active_layer.nextAll('.layer_panel').size();
var b_s = (next_size == 0) ? 0 : (next_size * 43);
// Set layers offset
var offset = -15;
$active_layer.css({ height: this.$el.height() - (t_s + b_s) + offset })
},
/* testing resize event */
_checkResize: function(e) {
if (this.resize) clearTimeout(this.resize);
var self = this;
this.resize = setTimeout(self._manageLayers, 100);
},
//////////////////////
// Dialog functions //
//////////////////////
show: function(modName) {
// Select the tab if it is not activated
var mod = this.getActivePane();
mod.tabs.activate(modName);
mod.panels.active(modName);
// Move right panel
this.movePanel();
},
// Move right panel
movePanel: function() {
var mod = this.getActivePane();
if (!mod) return false;
this._hideDropdowns();
// Get the action type and width
// of the active module
// var action = { module_event: narrow or show, module_width: 450 (integer) }
var active_pane = mod.panels.getActivePane();
var action = active_pane.getModuleAction();
// Show panel -> trigger
cdb.god.trigger('panel_action', action.type);
// Set open model attribute as true
this.model.set('open', true);
this.$el.animate({
width: action.width,
right: 0
}, this.animation_time, function() {
cdb.god.trigger("end_" + action.type);
});
},
hide: function(modName) {
var panel_width = this.$el.width();
// Hide the tab
var mod = this.getActivePane();
this._hideDropdowns();
// Hide panel -> trigger
cdb.god.trigger("panel_action", "hide");
// Set open model attribute as true
this.model.set('open', false);
this.$el.animate({
right: 63 - panel_width
}, this.animation_time);
},
_hideDropdowns: function() {
cdb.god.trigger("closeDialogs");
cdb.god.trigger("closeDialogs:color");
},
// l -> layer view
// open -> flag to know if it is necessary to open the panel
_switchTo: function(l, open) {
// If there is any change within layer tabs
if (this.activePane) this.activePane.unbind('tabChanged', null, null);
l.bind('tabChanged', this.movePanel, this);
// Check if switched layer is active one
if (this.activePane == l) {
if (open !== undefined) {
if (open === true) {
this.show(l.panels.activeTab);
} else {
this.hide();
}
} else {
if (this.model.get('open')) {
this.hide();
} else {
this.show(l.panels.activeTab);
}
}
}
// if not, activate it
if (this.activePane != l) {
this._hideLayerTooltip(l);
this.active(l.dataLayer.cid);
if (l.dataLayer.id && this.vis.get('active_layer_id') != l.dataLayer.id) {
this.vis.save('active_layer_id', l.dataLayer.id);
}
if (open === undefined) this.show(l.panels.activeTab);
}
// Send trigger
this.trigger('switch', l);
// Manage available layers
this._manageLayers();
},
_toggle: function(modName) {
// only hide if we click on active tab
if (this.model.get('open') && modName == this.getActivePane().panels.activeTab) {
this.hide(modName);
} else {
this.show(modName);
}
},
_openDeleteLayerDialog: function(layerView) {
var view = new cdb.editor.DeleteLayerView({
model: layerView.model
});
view.appendToBody();
},
setActiveWorkView: function(workView) {
this.hide();
this.model.set('activeWorkView', workView);
// Set active work view for all panes,
// not only for the current one
this.each(function(tab, pane){
pane.setActiveWorkView(workView);
})
},
clean: function() {
$(window).unbind('resize', this._checkResize);
if (this.resize) clearTimeout(this.resize);
this.removeTabs();
this._unbindSort();
this.elder('clean');
}
});