Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
@@ -0,0 +1,190 @@
var _ = require('underscore');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var AnalysisOnboardingLauncher = require('builder/components/onboardings/analysis/analysis-launcher');
var AnalysisNotifications = require('builder/editor/layers/analysis-views/analysis-notifications');
var REQUIRED_OPTS = [
'diDashboardHelpers',
'analysisDefinitionsCollection',
'analysisDefinitionNodesCollection',
'layerDefinitionsCollection',
'onboardings',
'userModel',
'visDefinitionModel'
];
/**
* Only manage **ANALYSIS NODES** and **ANALYSIS DEFINITION** actions between
* Deep-Insights (CARTO.js) and Builder
*
*/
module.exports = {
track: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._analysisDefinitionNodesCollection.each(function (analysisDefinitionNode) {
analysisDefinitionNode.queryRowsCollection.on('remove', this._invalidateMap, this);
}, this);
this._analysisDefinitionNodesCollection.on('add', this._onAnalysisDefinitionNodeAdded, this);
this._analysisDefinitionNodesCollection.on('change', this._onAnalysisDefinitionNodeChanged, this);
this._analysisDefinitionNodesCollection.on('change:id', this._onAnalysisDefinitionNodeIdChanged, this);
this._analysisDefinitionNodesCollection.on('remove', this._onAnalysisDefinitionNodeRemoved, this);
this._analysisDefinitionsCollection.on('add', this._onAnalysisDefinitionAdded, this);
this._analysisDefinitionsCollection.on('sync', this._onAnalysisDefinitionSync, this);
this._analysisDefinitionsCollection.each(this._analyseDefinition, this);
return this;
},
_onAnalysisDefinitionNodeAdded: function (node) {
node.queryRowsCollection.on('remove', this._invalidateMap, this);
this.analyseDefinitionNode(node);
},
_onAnalysisDefinitionNodeChanged: function (node) {
if (!this._hasUpdatedOnlyNodeAnalysisStatus(node)) {
// Only call analyse if there is a non-only-status change
this.analyseDefinitionNode(node);
}
},
_onAnalysisDefinitionNodeIdChanged: function (node) {
if (this._hasUpdatedOnlyNodeAnalysisId(node)) {
var analysis = this._diDashboardHelpers.getAnalysisByNodeId(node.previous('id'));
if (analysis) {
analysis.set('id', node.id);
}
}
},
_onAnalysisDefinitionNodeRemoved: function (node) {
node.queryRowsCollection.off('remove', this._invalidateMap, this);
var analysis = this._diDashboardHelpers.getAnalysisByNodeId(node.previous('id'));
if (analysis) {
analysis.set({avoidNotification: (node && !!node.get('avoidNotification'))}, {silent: true});
analysis.remove();
}
},
_onAnalysisDefinitionAdded: function (definition) {
this._analyseDefinition(definition);
},
_onAnalysisDefinitionSync: function (definition) {
this._analyseDefinition(definition);
},
_analyseDefinition: function (definition) {
var id = definition.get('node_id');
var node = this._analysisDefinitionNodesCollection.get(id);
this.analyseDefinitionNode(node);
},
analyseDefinitionNode: function (node) {
if (!this._hasUpdatedOnlyNodeAnalysisId(node)) {
var attrs = node.toJSON({ skipOptions: true });
this._diDashboardHelpers.analyse(attrs);
this._tryToSetupDefinitionNodesSync();
}
},
_hasUpdatedOnlyNodeAnalysisStatus: function (node) {
return node.hasChanged('status') && _.size(node.changed) === 1;
},
_hasUpdatedOnlyNodeAnalysisId: function (node) {
return node.hasChanged('id') && _.size(node.changed) === 1;
},
_tryToSetupDefinitionNodesSync: function () {
// Unfortunately have to try to setup sync until this point, since a node doesn't exist until after analyse call
this._analysisDefinitionNodesCollection.each(this._tryToSetupDefinitionNodeSync, this);
},
_tryToSetupDefinitionNodeSync: function (node) {
var isCachedAnalysis = this._isCachedAnalysis(node);
if (node.__syncSetup && !isCachedAnalysis) return; // only setup once
var analysis = this._diDashboardHelpers.getAnalysisByNodeId(node.id);
var layerDefinition = this._layerDefinitionsCollection.findOwnerOfAnalysisNode(node);
if (!analysis) return; // might not exist when method is called, so do nothing to allow retries
node.__syncSetup = true;
node.__initialization = true;
if (isCachedAnalysis) {
AnalysisOnboardingLauncher.launch(analysis.get('type'), node);
node.USER_SAVED = false;
return;
}
// Don't need to sync source nodes
if (analysis.get('type') !== 'source') {
AnalysisNotifications.track(analysis, layerDefinition);
var updateAnalysisQuerySchema = function () {
var query = analysis.get('query');
var status = analysis.get('status');
var error = analysis.get('error');
node.querySchemaModel.set({
status: 'unfetched',
query: query,
ready: status === 'ready'
});
node.queryGeometryModel.set({
status: 'unfetched',
query: query,
ready: status === 'ready'
});
node.set({
status: status,
error: error
});
if (status === 'ready') {
if (!node.__initialization) {
node.trigger('queryObjectsUpdated', node);
}
node.__initialization = false;
}
};
AnalysisOnboardingLauncher.init({
onboardings: this._onboardings,
userModel: this._userModel,
visDefinitionModel: this._visDefinitionModel
});
node.listenTo(analysis, 'change:status', function (model, status) {
if (status === 'ready' && node.USER_SAVED) {
AnalysisOnboardingLauncher.launch(analysis.get('type'), model);
node.USER_SAVED = false;
}
});
updateAnalysisQuerySchema();
node.listenTo(analysis, 'change:query change:status change:error', updateAnalysisQuerySchema);
node.listenToOnce(analysis, 'destroy', node.stopListening);
} else {
node.listenTo(node.querySchemaModel, 'resetDueToAlteredData', this._invalidateMap.bind(this));
}
},
_isCachedAnalysis: function (node) {
return node.hasChanged('status') && node.get('status') === 'ready' && node.previous('status') === 'launched';
},
_invalidateMap: function () {
this._diDashboardHelpers.invalidateMap();
}
};
@@ -0,0 +1,78 @@
var _ = require('underscore');
/**
* Create a class for providing the Deep Insights Helpers necessary
* for any implementation within deep-insights-integration.
*/
var diDashboardHelpers = function (deepInsightsDashboard) {
this._deepInsightsDashboard = deepInsightsDashboard;
return this;
};
_.extend(
diDashboardHelpers.prototype,
{
visMap: function () {
return this.getMap().map;
},
getMap: function () {
return this.getDashboard().getMap();
},
moveCartoDBLayer: function (from, to) {
this.visMap().moveCartoDBLayer(from, to);
},
reloadMap: function () {
this.getDashboard().reloadMap();
},
invalidateMap: function () {
this.getMap().reload();
},
forceResize: function () {
this.getDashboard().forceResize();
},
setBounds: function (bounds) {
this.getDashboard()._dashboard.vis.map.setBounds(bounds);
},
analyse: function (analysisDefinition) {
return this.getMap().analysis.analyse(analysisDefinition);
},
getAnalysisByNodeId: function (nodeId) {
return this.getMap().analysis.findNodeById(nodeId);
},
getLayer: function (id) {
return this.visMap().getLayerById(id);
},
getLayers: function () {
return this.visMap().layers;
},
getWidget: function (id) {
return this.getDashboard().getWidget(id);
},
getWidgets: function () {
return this.getDashboard().getWidgets();
},
getOverlays: function () {
return this.getMap().overlaysCollection;
},
getDashboard: function () {
return this._deepInsightsDashboard;
}
}
);
module.exports = diDashboardHelpers;
@@ -0,0 +1,214 @@
var $ = require('jquery');
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var ConfirmationView = require('builder/components/modals/confirmation/modal-confirmation-view');
var templateConfirmation = require('./start-edition-confirmation.tpl');
var templateEditGeometry = require('./edit-geometry.tpl');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var Notifier = require('builder/components/notifier/notifier');
var Router = require('builder/routes/router');
var EditFeatureOverlay = CoreView.extend({
MAX_VERTEXES: 1000,
className: 'CDB-Overlay',
type: 'custom',
events: {
'click .js-edit-feature': '_onEditButtonClicked'
},
initialize: function (options) {
if (!options.map) throw new Error('map is required');
if (!options.mapModeModel) throw new Error('mapModeModel is required');
if (!options.modals) throw new Error('modals is required');
this._map = options.map;
this._mapModeModel = options.mapModeModel;
this._modals = options.modals;
this._onEditButtonClicked = this._onEditButtonClicked.bind(this);
this.hide = this.hide.bind(this);
this.show = this.show.bind(this);
this._map.on('change:center', this.hide, this);
this._map.on('change:zoom', this.hide, this);
this._position = {
x: 0,
y: 0
};
},
render: function () {
this.clearSubViews();
var isEditable = this._isEditable() && !this._isAggregated();
this.$el.html(templateEditGeometry({
isEditable: isEditable,
hasAnalyses: this._hasAnalyses(),
isCustomQueryApplied: this._isCustomQueryApplied(),
isReadOnly: this._isReadOnly(),
disabled: this._disabledTooltip()
}));
if (!isEditable) {
var tooltip = new TipsyTooltipView({
el: this.$('.js-edit-feature'),
gravity: 's',
offset: 0,
title: function () {
return $(this).data('tooltip');
}
});
this.addView(tooltip);
this.undelegateEvents();
} else {
this.delegateEvents();
}
return this;
},
_disabledTooltip: function () {
var disabledLayerType;
if (this._hasAnalyses()) {
disabledLayerType = _t('editor.edit-feature.analysis');
} else if (this._isCustomQueryApplied()) {
disabledLayerType = _t('editor.edit-feature.custom-sql');
} else if (this._isReadOnly()) {
disabledLayerType = _t('editor.edit-feature.sync');
} else if (this._isAggregated()) {
disabledLayerType = _t('editor.edit-feature.aggregated');
}
return _t('editor.edit-feature.disabled', { disabledLayerType: disabledLayerType });
},
_isEditable: function () {
return this._featureDefinition && this._featureDefinition.isEditable();
},
_hasAnalyses: function () {
return this._featureDefinition && this._featureDefinition.hasAnalyses();
},
_isCustomQueryApplied: function () {
return this._featureDefinition && this._featureDefinition.isCustomQueryApplied();
},
_isReadOnly: function () {
return this._featureDefinition && this._featureDefinition.isReadOnly();
},
_isAggregated: function () {
return this._featureDefinition && this._featureDefinition.getLayerDefinition().hasAggregatedStyles();
},
hide: function (event) {
this.killEvent(event);
this.$el.hide();
},
show: function () {
this.$el.css({
width: '20px',
top: this._position.y,
left: this._position.x,
position: 'absolute'
});
this.$el.show();
},
setPosition: function (position) {
this._position = position;
},
setFeatureDefinition: function (featureDefinition) {
this._featureDefinition = featureDefinition;
},
getFeatureDefinition: function () {
return this._featureDefinition;
},
_getGeomCount: function (geojson) {
var count = 0;
_.each(geojson.coordinates, function (pol1, i) {
_.each(pol1, function (pol2, j) {
count = count + pol2.length;
});
});
return count;
},
_confirmStopEdition: function () {
var self = this;
this._modals.create(function (modalModel) {
return new ConfirmationView({
modalModel: modalModel,
template: templateConfirmation,
runAction: self._startEdition.bind(self)
});
});
},
_startEdition: function () {
// Exit editing feature mode to re-enter this mode again
if (this._mapModeModel.isEditingFeatureMode()) {
this._mapModeModel.enterViewingMode();
}
this._mapModeModel.enterEditingFeatureMode(this._featureDefinition);
this._featureDefinition && Router.editFeature(this._featureDefinition);
},
_onEditButtonClicked: function (ev) {
if (!this._featureDefinition) {
Router.goToDefaultRoute();
return;
}
this.killEvent(ev);
this.hide();
if (!this._isEditable()) {
return false;
}
var notification = Notifier.addNotification({
status: 'loading',
info: _t('notifications.edit-feature.edit.loading'),
closable: false
});
this._featureDefinition && this._featureDefinition.fetch({
success: function () {
Notifier.removeNotification(notification);
var geojson = JSON.parse(this._featureDefinition.get('the_geom'));
if (this._getGeomCount(geojson) > this.MAX_VERTEXES) {
this._confirmStopEdition();
} else {
this._startEdition();
}
}.bind(this),
error: function () {
notification.set({
status: 'error',
info: _t('notifications.edit-feature.edit.error'),
closable: true,
delay: Notifier.DEFAULT_DELAY
});
}
});
}
});
module.exports = EditFeatureOverlay;
@@ -0,0 +1,7 @@
<button class="CDB-Attribution-button js-edit-feature
<% if (!isEditable) { %>is-disabled<% } %>
<% if (hasAnalyses) { %>t-hasAnalyses<% } %>
<% if (isCustomQueryApplied) { %>t-isCustomQueryApplied<% } %>
<% if (isReadOnly) { %>t-isReadOnly<% } %>" data-tooltip="<%- disabled %>">
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M10 22.91l1.217-3.65 8.924-8.924c.448-.448 1.177-.448 1.624 0l.81.81c.448.448.448 1.175 0 1.623l-8.923 8.924L10 22.91zm10.952-8.518l-2.434-2.434 2.434 2.434zm-7.3 7.302l-2.435-2.434 2.434 2.434z" stroke="#636D72" fill="none" fill-rule="evenodd"/></svg>
</button>
@@ -0,0 +1,143 @@
var $ = require('jquery');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var FeatureDefinitionModel = require('builder/data/feature-definition-model');
var AppNotifications = require('builder/app-notifications');
var REQUIRED_OPTS = [
'configModel',
'diDashboardHelpers',
'editFeatureOverlay',
'layerDefinitionsCollection',
'mapModeModel',
'userModel'
];
/**
* Only manage **FEATURES** actions or behaviours between Deep-Insights
* (CARTO.js) and Builder
*
*/
module.exports = {
track: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
var visMap = this._diDashboardHelpers.visMap();
var mapModel = this._diDashboardHelpers.getMap();
this._mapModeModel.on('change:mode', this._onMapModeChanged, this);
this._mapModeModel.on('reloadVis', function () {
this._diDashboardHelpers.invalidateMap();
}, this);
mapModel.on('click', this._onFeatureOut, this);
visMap.on('featureClick', this._onFeatureClicked, this);
visMap.on('featureError', this._onFeatureError, this);
},
_onFeatureError: function (error) {
if (error && error.type === 'limit') {
AppNotifications.addNotification({
type: 'interactivity'
});
}
},
_onFeatureOut: function () {
this._editFeatureOverlay.hide();
},
_onFeatureClicked: function (event) {
var layerId = event.layer.id;
var featureId = event.feature.cartodb_id;
var position = event.position;
var layerDefinitionModel = this._layerDefinitionsCollection.get(layerId);
var isFeatureBeingEdited = false;
var featureDefinition = new FeatureDefinitionModel({
cartodb_id: featureId
}, {
configModel: this._configModel,
layerDefinitionModel: layerDefinitionModel,
userModel: this._userModel
});
if (this._mapModeModel.isEditingFeatureMode()) {
var editingFeatureDefinitionModel = this._mapModeModel.getFeatureDefinition();
isFeatureBeingEdited = featureDefinition.isEqual(editingFeatureDefinitionModel);
}
if (!isFeatureBeingEdited) {
this._editFeatureOverlay.setPosition(position);
this._editFeatureOverlay.setFeatureDefinition(featureDefinition);
this._editFeatureOverlay
.render()
.show();
}
},
_onMapModeChanged: function (mapModeModel) {
var map = this._diDashboardHelpers.visMap();
var featureDefinition;
var geometry;
// VIEWING MODE
if (mapModeModel.isViewingMode()) {
map.stopDrawingGeometry();
map.stopEditingGeometry();
}
// DRAWING FEATURES
if (mapModeModel.isDrawingFeatureMode()) {
featureDefinition = mapModeModel.getFeatureDefinition();
if (featureDefinition.isPoint()) {
geometry = map.drawPoint();
} else if (featureDefinition.isLine()) {
geometry = map.drawPolyline();
} else if (featureDefinition.isPolygon()) {
geometry = map.drawPolygon();
}
if (!geometry) {
throw new Error("couldn't get geometry for feature of type " + featureDefinition.get('type'));
}
}
// EDITING FEATURES
if (mapModeModel.isEditingFeatureMode()) {
featureDefinition = mapModeModel.getFeatureDefinition();
var geojson = JSON.parse(featureDefinition.get('the_geom'));
geometry = map.editGeometry(geojson);
}
if (featureDefinition && geometry) {
this._bindGeometryToFeatureDefinition(geometry, featureDefinition);
featureDefinition.on('save', function () {
if (featureDefinition.hasBeenChangedAfterLastSaved('the_geom') || featureDefinition.hasBeenChangedAfterLastSaved('cartodb_id')) {
this._diDashboardHelpers.invalidateMap();
geometry.setCoordinatesFromGeoJSON(JSON.parse(featureDefinition.get('the_geom')));
}
}, this);
featureDefinition.on('remove', function () {
this._diDashboardHelpers.invalidateMap();
}, this);
}
},
_bindGeometryToFeatureDefinition: function (geometry, featureDefinition) {
geometry.on('change', function () {
if (geometry.isComplete()) {
$('.js-editOverlay').fadeOut(200, function () {
$('.js-editOverlay').remove();
});
var geojson = geometry.toGeoJSON();
geojson = geojson.geometry || geojson;
featureDefinition.set({
the_geom: JSON.stringify(geojson)
});
featureDefinition.trigger('updateFeature');
}
});
}
};
@@ -0,0 +1,395 @@
var _ = require('underscore');
var Backbone = require('backbone');
var LegendManager = require('./legend-manager');
var linkLayerInfowindow = require('./link-layer-infowindow');
var linkLayerTooltip = require('./link-layer-tooltip');
var layerTypesAndKinds = require('builder/data/layer-types-and-kinds');
var Notifier = require('builder/components/notifier/notifier');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var NotificationErrorMessageHandler = require('builder/editor/layers/notification-error-message-handler');
var basemapProvidersAndCategories = require('builder/data/basemap-providers-and-categories');
var REQUIRED_OPTS = [
'analysisDefinitionNodesCollection',
'editFeatureOverlay',
'layerDefinitionsCollection',
'legendDefinitionsCollection',
'diDashboardHelpers'
];
var LAYER_TYPE_TO_LAYER_CREATE_METHOD = {
'cartodb': 'createCartoDBLayer',
'gmapsbase': 'createGMapsBaseLayer',
'plain': 'createPlainLayer',
'tiled': 'createTileLayer',
'torque': 'createTorqueLayer',
'wms': 'createWMSLayer'
};
var CARTODBJS_TO_CARTODB_ATTRIBUTE_MAPPINGS = {
'layer_name': ['table_name_alias', 'table_name']
};
var BLACKLISTED_LAYER_DEFINITION_ATTRS = {
'all': [ 'letter', 'kind' ],
'Tiled': [ 'category', 'selected', 'highlighted' ],
'CartoDB': [ 'color', 'letter' ],
'torque': [ 'color', 'letter' ]
};
/**
* Only manage **LAYER** actions between Deep-Insights (CARTO.js) and Builder
*
*/
var LayersIntegration = {
track: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._visMap = this._diDashboardHelpers.visMap();
this._layerDefinitionsCollection.each(this._linkLayerErrors, this);
this._layerDefinitionsCollection.on('add', this._onLayerDefinitionAdded, this);
this._layerDefinitionsCollection.on('sync', this._onLayerDefinitionSynced, this);
this._layerDefinitionsCollection.on('change', this._onLayerDefinitionChanged, this);
this._layerDefinitionsCollection.on('remove', this._onLayerDefinitionRemoved, this);
this._layerDefinitionsCollection.on('layerMoved', this._onLayerDefinitionMoved, this);
this._layerDefinitionsCollection.on('baseLayerChanged', this._onBaseLayerChanged, this);
this._layerDefinitionsCollection.each(function (layerDefModel) {
LegendManager.track(layerDefModel);
linkLayerInfowindow(layerDefModel, this._visMap);
linkLayerTooltip(layerDefModel, this._visMap);
if (layerDefModel.has('source')) {
this._resetStylesIfNoneApplied(layerDefModel);
}
}, this);
// In order to sync layer selector and layer visibility
this._diDashboardHelpers.getLayers().on('change:visible', function (layer, visible) {
var layerDefModel = this._layerDefinitionsCollection.findWhere({id: layer.id});
if (layerDefModel) {
if (layerDefModel.get('visible') !== visible) {
layerDefModel.save({visible: visible});
}
}
}, this);
return this;
},
_onLayerDefinitionRemoved: function (m) {
if (!m.isNew()) {
var layer = this._diDashboardHelpers.getLayer(m.id);
layer && layer.remove();
}
},
_onLayerDefinitionMoved: function (m, from, to) {
this._diDashboardHelpers.moveCartoDBLayer(from, to);
},
_createLayer: function (layerDefModel, options) {
options = options || {};
var attrs = JSON.parse(JSON.stringify(layerDefModel.attributes)); // deep clone
attrs = this._adaptAttrsToCDBjs(layerDefModel.get('type'), attrs);
// create the legends for the new layer
var legends = this._legendDefinitionsCollection.findByLayerDefModel(layerDefModel);
if (legends.length > 0) {
attrs.legends = _.map(legends, function (legend) {
return legend.toJSON();
});
}
var createMethodName = LAYER_TYPE_TO_LAYER_CREATE_METHOD[attrs.type.toLowerCase()];
if (!createMethodName) throw new Error('no create method name found for type ' + attrs.type);
if (attrs.source) {
// Make sure the analysis is created first
var nodeDefModel = this._analysisDefinitionNodesCollection.get(attrs.source);
// Dependency with another integration
this.trigger('onLayerCreation', nodeDefModel);
// Set analysis model instead of the string ID source
attrs.source = this._diDashboardHelpers.getAnalysisByNodeId(attrs.source);
}
var layerPosition = this._layerDefinitionsCollection.indexOf(layerDefModel);
this._visMap[createMethodName](attrs, _.extend({
at: layerPosition
}, options));
linkLayerInfowindow(layerDefModel, this._visMap);
linkLayerTooltip(layerDefModel, this._visMap);
LegendManager.track(layerDefModel);
this._linkLayerErrors(layerDefModel);
},
_linkLayerErrors: function (m) {
var layer = this._diDashboardHelpers.getLayer(m.id);
if (layer) {
if (layer.get('error')) {
this._setLayerError(m, layer.get('error'));
}
layer.on('change:error', function (model, cdbError) {
this._setLayerError(m, cdbError);
}, this);
}
},
_setLayerError: function (layerDefinitionModel, cdbError) {
var notification = Notifier.getNotification(layerDefinitionModel.id);
var mainErrorMessage = layerDefinitionModel.getName() + ': ' + (cdbError && cdbError.message);
if (!cdbError) {
layerDefinitionModel.unset('error');
notification && Notifier.removeNotification(notification);
return;
}
var errorMessage = NotificationErrorMessageHandler.extractError(mainErrorMessage);
if (notification) {
notification.update({
status: errorMessage.type,
info: errorMessage.message
});
} else {
Notifier.addNotification({
id: layerDefinitionModel.id,
status: errorMessage.type,
closable: true,
button: false,
info: errorMessage.message
});
}
if (cdbError.subtype === 'turbo-carto') {
var line;
try {
line = cdbError.context.source.start.line;
} catch (error) {}
layerDefinitionModel.set('error', {
type: cdbError.type,
subtype: cdbError.subtype,
line: line,
message: cdbError.message
});
} else if (errorMessage) {
layerDefinitionModel.set('error', {
type: errorMessage.type,
subtype: cdbError.subtype,
message: errorMessage.message
});
}
},
_onLayerDefinitionAdded: function (m, c, opts) {
// Base and labels layers are synced in a separate method
if (!layerTypesAndKinds.isTypeDataLayer(m.get('type'))) {
return;
}
// If added but not yet saved, postpone the creation until persisted (see sync listener)
if (!m.isNew()) {
if (!this._diDashboardHelpers.getLayer(m.id)) {
this._createLayer(m);
} else {
// we need to sync model positions
this._tryUpdateLayerPosition(m);
}
}
},
_tryUpdateLayerPosition: function (m) {
var builderPosition = this._layerDefinitionsCollection.indexOf(m);
var cdbLayer = this._diDashboardHelpers.getLayer(m.id);
var cdbPosition;
if (cdbLayer) {
cdbPosition = this._diDashboardHelpers.getLayers().indexOf(cdbLayer);
}
var indexChanges = m.isDataLayer() && cdbPosition > 0 && builderPosition > 0 && builderPosition !== cdbPosition;
if (indexChanges) {
this._diDashboardHelpers.moveCartoDBLayer(cdbPosition, builderPosition);
}
},
_onLayerDefinitionSynced: function (m) {
// Base and labels layers are synced in a separate method
if (!layerTypesAndKinds.isTypeDataLayer(m.get('type'))) {
return;
}
if (!this._diDashboardHelpers.getLayer(m.id)) {
this._createLayer(m);
}
},
_onLayerDefinitionChanged: function (layerDefinitionModel, changedAttributes) {
var attrs = layerDefinitionModel.changedAttributes();
var attrsNames = _.keys(attrs);
// Base and labels layers are synced in a separate method
if (!layerTypesAndKinds.isTypeDataLayer(layerDefinitionModel.get('type'))) {
return;
}
// return if only the 'error' attribute has changed (no need to sync anything)
if (attrsNames.length === 1 && attrsNames[0] === 'error') {
return;
}
var layer = this._diDashboardHelpers.getLayer(layerDefinitionModel.id);
if (!layerDefinitionModel.isNew()) {
if (!layer) {
this._createLayer(layerDefinitionModel);
return;
}
if (attrs.type) {
layer.remove();
this._createLayer(layerDefinitionModel);
} else {
if (layerDefinitionModel.get('source') && !layer.get('source')) {
attrs.source = layerDefinitionModel.get('source');
}
var onlySourceChanged = attrs.source && _.keys(attrs).length === 1;
if (attrs.source) {
// Set analysis model instead of the string ID source
attrs.source = this._diDashboardHelpers.getAnalysisByNodeId(attrs.source);
// Set source with setSource method
layer.setSource(attrs.source, { silent: !onlySourceChanged });
// Remove source form attrs to avoid updating source
delete attrs.source;
}
attrs = this._adaptAttrsToCDBjs(layerDefinitionModel.get('type'), attrs);
layer.update(attrs, { silent: onlySourceChanged });
}
// Find an animated layer if exists
var animatedLayerDefinitionModel = this._layerDefinitionsCollection.find(function (model) {
return model && model.styleModel && model.styleModel.get('type') === 'animation';
});
// Dependency with widgets-integration
// If there is an animated layer, use that layer, else use the provided layer
this.trigger('onLayerChanged', animatedLayerDefinitionModel || layerDefinitionModel);
}
},
_onBaseLayerChanged: function () {
var baseLayerDefinition = this._layerDefinitionsCollection.getBaseLayer();
var newBaseLayerAttrs = baseLayerDefinition.changedAttributes();
var newBaseLayerType = baseLayerDefinition.get('type');
var newMapProvider = basemapProvidersAndCategories.getProvider(newBaseLayerType);
var mapProviderChanged = false;
if (baseLayerDefinition.hasChanged('type')) {
var previousBaseLayerType = baseLayerDefinition.previous('type');
var previousMapProvider = basemapProvidersAndCategories.getProvider(previousBaseLayerType);
mapProviderChanged = previousMapProvider !== newMapProvider;
}
// If the map provider has changed (eg: Leaflet -> Google Maps), we add/update/remove base and
// labels layers silently so that CartoDB.js doesn't pick up those changes and tries to add/update/remove
// layers until the new map provider has been set
var handleLayersSilently = mapProviderChanged;
// Base layer
var cdbjsLayer = this._diDashboardHelpers.getLayer(baseLayerDefinition.id);
// If the type of base layer has changed. eg: Tiled -> Plain
if (newBaseLayerAttrs.type) {
cdbjsLayer.remove({ silent: handleLayersSilently });
this._createLayer(baseLayerDefinition, { silent: handleLayersSilently });
} else {
cdbjsLayer.update(this._adaptAttrsToCDBjs(baseLayerDefinition.get('type'), newBaseLayerAttrs), {
silent: handleLayersSilently
});
}
// Labels layer
var labelsLayerDefinition = this._layerDefinitionsCollection.getLabelsLayer();
var cdbjsTopLayer = this._diDashboardHelpers.getLayers().last();
var cdbjsHasLabelsLayer = cdbjsTopLayer.get('type') === 'Tiled';
if (labelsLayerDefinition) {
if (cdbjsHasLabelsLayer) {
var changedAttrs = labelsLayerDefinition.changedAttributes();
if (changedAttrs) {
cdbjsTopLayer.update(this._adaptAttrsToCDBjs(labelsLayerDefinition.get('type'), changedAttrs), {
silent: handleLayersSilently
});
}
} else {
this._createLayer(labelsLayerDefinition, { silent: handleLayersSilently });
}
} else if (cdbjsHasLabelsLayer) {
cdbjsTopLayer.remove({ silent: handleLayersSilently });
}
// Map provider
this._visMap.set('provider', newMapProvider);
if (handleLayersSilently) {
// Reload map if everything (previously) was done silently
this._diDashboardHelpers.reloadMap();
}
// Render again the edit-feature-overlay, in order to
// decide if delegate or not events
this._editFeatureOverlay.render();
// Dependency with map-integration class
this.trigger('onBaseLayerChanged');
},
_resetStylesIfNoneApplied: function (layerDefModel) {
var nodeDefModel = layerDefModel.getAnalysisDefinitionNodeModel();
var nodeModel = this._diDashboardHelpers.getAnalysisByNodeId(layerDefModel.get('source'));
var isAnalysisNode = nodeModel && nodeModel.get('type') !== 'source';
var isDone = nodeModel && nodeModel.isDone();
var queryGeometryModel = nodeDefModel && nodeDefModel.queryGeometryModel;
var styleModel = layerDefModel.styleModel;
if (isAnalysisNode && styleModel.hasNoneStyles() && isDone) {
var simpleGeom = queryGeometryModel.get('simple_geom');
var applyDefaultStyles = function () {
simpleGeom = queryGeometryModel.get('simple_geom');
styleModel.setDefaultPropertiesByType('simple', simpleGeom);
};
if (!simpleGeom) {
queryGeometryModel.once('change:simple_geom', applyDefaultStyles, this);
queryGeometryModel.fetch();
} else {
applyDefaultStyles();
}
}
},
_adaptAttrsToCDBjs: function (layerType, attrs) {
attrs = _.omit(attrs, BLACKLISTED_LAYER_DEFINITION_ATTRS['all'], BLACKLISTED_LAYER_DEFINITION_ATTRS[layerType]);
_.each(CARTODBJS_TO_CARTODB_ATTRIBUTE_MAPPINGS, function (cdbAttrs, cdbjsAttr) {
_.each(cdbAttrs, function (cdbAttr) {
if (attrs[cdbAttr] && !attrs[cdbjsAttr]) {
attrs[cdbjsAttr] = attrs[cdbAttr];
}
});
});
return attrs;
}
};
_.extend(LayersIntegration, Backbone.Events);
module.exports = LayersIntegration;
@@ -0,0 +1,170 @@
var _ = require('underscore');
var LegendFactory = require('builder/editor/layers/layer-content-views/legend/legend-factory');
var LegendColorHelper = require('builder/editor/layers/layer-content-views/legend/form/legend-color-helper');
var LegendTypes = require('builder/editor/layers/layer-content-views/legend/legend-types');
var StyleHelper = require('builder/helpers/style');
var DEFAULT_BUBBLES_COLOR = '#999999';
function _createLegendForAnimation (layerDefModel, color) {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CATEGORY, function () {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CHOROPLETH, function () {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CUSTOM_CHOROPLETH, function () {
var items = StyleHelper.getStyleCategories(layerDefModel.styleModel);
LegendFactory.createLegend(layerDefModel, LegendTypes.TORQUE, {
title: color.attribute,
items: items
});
});
});
});
}
function _createLegendForHeatmap (layerDefModel) {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CATEGORY, function () {
LegendFactory.removeLegend(layerDefModel, LegendTypes.TORQUE, function () {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CHOROPLETH, function () {
var colors = StyleHelper.getHeatmapColors(layerDefModel);
LegendFactory.createLegend(layerDefModel, LegendTypes.CUSTOM_CHOROPLETH, {
title: _t('editor.legend.pixel-title'),
colors: colors
});
});
});
});
}
function _createCategoryLegend (layerDefModel, color) {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CHOROPLETH, function () {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CUSTOM_CHOROPLETH, function () {
if (layerDefModel.isAutoStyleApplied()) {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CATEGORY);
} else {
LegendFactory.createLegend(layerDefModel, LegendTypes.CATEGORY, { title: color.attribute });
}
});
});
}
function _createChoroplethLegend (layerDefModel, color) {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CATEGORY, function () {
LegendFactory.removeLegend(layerDefModel, LegendTypes.TORQUE, function () {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CUSTOM_CHOROPLETH, function () {
if (layerDefModel.isAutoStyleApplied()) {
LegendFactory.removeLegend(layerDefModel, LegendTypes.CHOROPLETH);
} else {
LegendFactory.createLegend(layerDefModel, LegendTypes.CHOROPLETH, { title: color.attribute });
}
});
});
});
}
function _createColorLegend (layerDefModel, color) {
var styleModel = layerDefModel.styleModel;
if (styleModel.isAnimation()) {
_createLegendForAnimation(layerDefModel, color);
} else if (styleModel.isHeatmap()) {
_createLegendForHeatmap(layerDefModel);
} else {
var usesCategory = (color && color.quantification === 'category');
var attributeTypeIsString = (color.attribute_type && color.attribute_type === 'string');
var attributeTypeIsBoolean = (color.attribute_type && color.attribute_type === 'boolean');
if (usesCategory || attributeTypeIsString || attributeTypeIsBoolean) {
_createCategoryLegend(layerDefModel, color);
} else {
_createChoroplethLegend(layerDefModel, color);
}
}
}
function _updateSizeLegend (layerDefModel, size, color) {
if (size && size.attribute !== undefined) {
var attrs = { title: size.attribute };
if (!layerDefModel.isAutoStyleApplied()) {
// Do not apply the fill color of an autostyle ramp
attrs.fillColor = color && (color.fixed || color.range)
? LegendColorHelper.getBubbles(color).color.fixed
: DEFAULT_BUBBLES_COLOR;
}
var hasBubbleLegend = LegendFactory.find(layerDefModel, LegendTypes.BUBBLE);
if (hasBubbleLegend) {
LegendFactory.createLegend(layerDefModel, LegendTypes.BUBBLE, attrs);
}
} else {
LegendFactory.removeLegend(layerDefModel, LegendTypes.BUBBLE);
}
}
function _updateColorLegend (layerDefModel, color) {
var colorLegendTypes = [LegendTypes.CATEGORY, LegendTypes.TORQUE, LegendTypes.CHOROPLETH, LegendTypes.CUSTOM_CHOROPLETH];
if (color && color.attribute !== undefined) {
var hasSomeLegend = [];
_.each(colorLegendTypes, function (legendType) {
var hasLegend = LegendFactory.find(layerDefModel, legendType);
if (hasLegend) {
hasSomeLegend.push(hasLegend);
}
});
var hasCustomLegend = LegendFactory.find(layerDefModel, LegendTypes.CUSTOM);
if (!hasCustomLegend && hasSomeLegend.length) {
_createColorLegend(layerDefModel, color);
}
} else {
_.each(colorLegendTypes, function (legendType) {
LegendFactory.removeLegend(layerDefModel, legendType);
});
}
}
var onChange = function (layerDefModel) {
var color = StyleHelper.getColor(layerDefModel.styleModel);
var size = StyleHelper.getSize(layerDefModel.styleModel);
if (color == null && size == null) return;
if (LegendFactory.isEnabledType('size')) {
_updateSizeLegend(layerDefModel, size, color);
}
if (LegendFactory.isEnabledType('color')) {
_updateColorLegend(layerDefModel, color);
}
};
module.exports = {
track: function (layerDefModel) {
var onStyleChange = function () {
onChange(layerDefModel);
};
var styleModel = layerDefModel.styleModel;
layerDefModel.on('change:cartocss', onChange);
if (styleModel) {
styleModel.on('style:update', onStyleChange);
// For torque legends, for agreggations with the same range, there is no change on the cartocss
// so we need to force the onChange for these.
styleModel.on('change', function () {
var hasChangedCartocss = layerDefModel.hasChanged('cartocss');
if (!hasChangedCartocss && styleModel.isAnimation()) {
onChange(layerDefModel);
}
});
}
layerDefModel.on('destroy', function () {
layerDefModel.off('change:cartocss', onChange);
styleModel && styleModel.off('style:update', onStyleChange);
});
}
};
@@ -0,0 +1,64 @@
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'diDashboardHelpers',
'legendDefinitionsCollection'
];
/**
* Only manage **LEGENDS** actions between Deep-Insights (CARTO.js) and Builder
*
*/
module.exports = {
track: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._legendDefinitionsCollection.on('add', this._onLegendDefinitionAdded, this);
this._legendDefinitionsCollection.on('change', this._onLegendDefinitionChanged, this);
this._legendDefinitionsCollection.on('remove', this._onLegendDefinitionRemoved, this);
return this;
},
_onLegendDefinitionAdded: function (m) {
var layerDefModel = m.layerDefinitionModel;
var layer = this._diDashboardHelpers.getLayer(layerDefModel.id);
var type = m.get('type');
var legend;
if (layer && layer.legends) {
legend = layer.legends[type];
if (legend) {
legend.reset();
legend.set(m.getAttributes());
legend.show();
}
}
},
_onLegendDefinitionRemoved: function (m) {
var layerDefModel = m.layerDefinitionModel;
var layer = this._diDashboardHelpers.getLayer(layerDefModel.id);
var type = m.get('type');
var legend;
if (layer && layer.legends) {
legend = layer.legends[type];
legend && legend.hide();
}
},
_onLegendDefinitionChanged: function (m) {
var layerDefModel = m.layerDefinitionModel;
var layer = this._diDashboardHelpers.getLayer(layerDefModel.id);
var type = m.get('type');
var legend;
if (layer && layer.legends) {
legend = layer.legends[type];
if (legend) {
legend.reset();
legend.set(m.getAttributes());
}
}
}
};
@@ -0,0 +1,48 @@
var infowindowNoneTemplate = require('builder/mustache-templates/infowindows/infowindow_none.tpl');
module.exports = function linkLayerInfowindow (layerDef, visMap) {
if (!layerDef.infowindowModel) return;
function updateInfowindow (infowindowModel) {
var attrs = JSON.parse(JSON.stringify(infowindowModel.attributes));
if (infowindowModel.isEmptyTemplate()) {
var node = layerDef.getAnalysisDefinitionNodeModel();
var cartodb_id = node && node.querySchemaModel.columnsCollection.find(function (m) {
return m.get('name') === 'cartodb_id';
});
if (cartodb_id) {
attrs.fields = [{
name: 'cartodb_id',
title: true,
position: 0
}];
attrs.template = infowindowNoneTemplate({
title: _t('editor.layers.infowindow.no-fields'),
subtitle: _t('editor.layers.infowindow.select-fields')
});
}
}
var infowindow = visMap.getLayerById(layerDef.id).infowindow;
// some layers like basemaps, torque, or aggregated don't have infowindows so skip update
if (infowindow) {
infowindow.update(attrs);
}
}
function onChange (infowindowModel) {
updateInfowindow(infowindowModel);
}
updateInfowindow(layerDef.infowindowModel);
layerDef.infowindowModel.bind('change', onChange);
layerDef.bind('destroy', function () {
layerDef.infowindowModel.unbind('change', onChange);
});
};
@@ -0,0 +1,22 @@
module.exports = function linkLayerTooltip (layerDef, visMap) {
if (!layerDef.tooltipModel) return;
function onChange (tooltipModel) {
var attrs = JSON.parse(JSON.stringify(tooltipModel.attributes));
var tooltip = visMap.getLayerById(layerDef.id).tooltip;
// some layers like basemaps, torque, or aggregated don't have infowindows
// so skip update
tooltip && tooltip.update(attrs);
}
var tooltipModel = layerDef.tooltipModel;
if (tooltipModel.get('template_name')) {
tooltipModel.setTemplate(tooltipModel.get('template_name'));
}
tooltipModel.bind('change', onChange);
layerDef.bind('destroy', function () {
tooltipModel.unbind('change', onChange);
});
};
@@ -0,0 +1,148 @@
var _ = require('underscore');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var VisNotifications = require('builder/vis-notifications');
var AppNotifications = require('builder/app-notifications');
var REQUIRED_OPTS = [
'diDashboardHelpers',
'editorModel',
'mapDefinitionModel',
'stateDefinitionModel',
'visDefinitionModel'
];
/**
* Only manage **MAP**, **STATE** and **VIS** actions between Deep-Insights (CARTO.js) and Builder
*
*/
module.exports = {
track: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._map = this._diDashboardHelpers.getMap();
this._vis = this._diDashboardHelpers.visMap();
this._mapDefinitionModel.on('change:minZoom change:maxZoom', _.debounce(this._onMinMaxZoomChanged.bind(this), 300), this);
this._mapDefinitionModel.on('change:scrollwheel', this._onScrollWheelChanged, this);
this._mapDefinitionModel.on('change:legends', this._onLegendsChanged, this);
this._mapDefinitionModel.on('change:layer_selector', this._onLayerSelectorChanged, this);
var saveStateDefinition = _.debounce(this._saveStateDefinition.bind(this), 500);
this._diDashboardHelpers.getDashboard().onStateChanged(saveStateDefinition);
this._stateDefinitionModel.on('boundsSet', this._onBoundsSet, this);
this._vis.on('mapViewSizeChanged', this._setMapViewSize, this);
this._editorModel.on('change:settingsView', this._onEditorSettingsChanged, this);
this._editorModel.on('change:edition', this._onEditionChanged, this);
this._map.on('reload', this._visReload, this);
this._map.on('change:error', this._visErrorChange, this);
this._vis.on('featureError error:tile error:limit', function (error) {
AppNotifications.addNotification(error);
}, this);
VisNotifications.track(this._map);
this._onScrollWheelChanged();
// Needed to image export feature
this._getVisMetadata();
this.setMapConverters();
this._setMapViewSize();
return this;
},
_onMinMaxZoomChanged: function () {
var currentZoom = this._vis.get('zoom');
var maxZoom = this._mapDefinitionModel.get('maxZoom');
var minZoom = this._mapDefinitionModel.get('minZoom');
this._vis.set({
minZoom: minZoom,
maxZoom: maxZoom,
zoom: Math.min(currentZoom, maxZoom)
});
},
_saveStateDefinition: function () {
var state = this._diDashboardHelpers.getDashboard().getState();
this._stateDefinitionModel.updateState(state);
this._getVisMetadata();
},
_onScrollWheelChanged: function () {
var scrollwheel = this._mapDefinitionModel.get('scrollwheel');
var method = scrollwheel ? 'enableScrollWheel' : 'disableScrollWheel';
var map = this._vis;
map && map[method] && map[method]();
},
_onLegendsChanged: function () {
var legends = this._mapDefinitionModel.get('legends');
this._map.settings.set('showLegends', legends);
},
_onLayerSelectorChanged: function () {
var layerSelector = this._mapDefinitionModel.get('layer_selector');
this._map.settings.set('showLayerSelector', layerSelector);
},
_setMapViewSize: function () {
this._mapDefinitionModel.setMapViewSize(
this._vis.getMapViewSize()
);
},
setMapConverters: function () {
var map = this._diDashboardHelpers.visMap();
this._mapDefinitionModel.setConverters({
pixelToLatLng: map.pixelToLatLng(),
latLngToPixel: map.latLngToPixel()
});
},
_onBoundsSet: function (bounds) {
this._diDashboardHelpers.setBounds(bounds);
},
_onEditorSettingsChanged: function () {
var settingsView = this._editorModel.get('settingsView');
this._map.settings.set('layerSelectorEnabled', settingsView);
},
_onEditionChanged: function () {
this._diDashboardHelpers.forceResize();
},
_getVisMetadata: function () {
var vis = this._map;
var map = this._diDashboardHelpers.visMap();
var layers = this._diDashboardHelpers.getLayers();
var imageExportMetadata = {
zoom: map.get('zoom'),
mapType: map.getBaseLayer().get('baseType'),
style: layers.at(0).get('style'),
attribution: map.get('attribution'),
provider: map.get('provider')
};
this._mapDefinitionModel.setImageExportMetadata(imageExportMetadata);
this._mapDefinitionModel.setStaticImageURLTemplate(vis.getStaticImageURL.bind(vis));
},
_visReload: function () {
this._getVisMetadata();
this._visDefinitionModel.trigger('vis:reload');
this._visDefinitionModel.recordChange();
},
_visErrorChange: function () {
this._visDefinitionModel &&
this._visDefinitionModel.trigger('vis:error', this._map.get('error'));
}
};
@@ -0,0 +1,32 @@
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'diDashboardHelpers',
'overlayDefinitionsCollection'
];
/**
* Only manage **OVERLAYS** actions between Deep-Insights (CARTO.js) and Builder
*
*/
module.exports = {
track: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._overlayDefinitionsCollection.on('add', this._onOverlayDefinitionAdded, this);
this._overlayDefinitionsCollection.on('remove', this._onOverlayDefinitionRemoved, this);
return this;
},
_onOverlayDefinitionAdded: function (mdl) {
this._diDashboardHelpers.getOverlays().add(mdl.toJSON());
},
_onOverlayDefinitionRemoved: function (mdl) {
var collection = this._diDashboardHelpers.getOverlays();
var overlay = collection.findWhere({ type: mdl.get('type') });
overlay && collection.remove(overlay);
}
};
@@ -0,0 +1,27 @@
<div class="u-flex u-justifyCenter">
<div class="Modal-inner Modal-inner--grid u-flex u-justifyCenter">
<div class="Modal-icon">
<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg">
<path d="M1 25l2.262-6.787 16.59-16.59c.832-.83 2.185-.83 3.016 0l1.508 1.51c.83.83.83 2.182 0 3.015l-16.59 16.59L1 25zM21.36 9.165L16.835 4.64l4.525 4.525zM7.787 22.738l-4.525-4.525 4.525 4.525z" stroke="#F19243" fill="none" fill-rule="evenodd"/>
</svg>
</div>
<div>
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--m"><%- _t('components.modals.edit-feature.confirmation.title') %></h2>
<p class="CDB-Text CDB-Size-medium u-altTextColor"><%- _t('components.modals.edit-feature.confirmation.desc') %></p>
<ul class="Modal-listActions u-flex u-alignCenter">
<li class="Modal-listActionsitem">
<button class="CDB-Button CDB-Button--secondary CDB-Button--big js-cancel">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
<%- _t('components.modals.edit-feature.confirmation.cancel') %>
</span>
</button>
</li>
<li class="Modal-listActionsitem">
<button class="CDB-Button CDB-Button--primary CDB-Button--big js-confirm">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.edit-feature.confirmation.continue') %></span>
</button>
</li>
</ul>
</div>
</div>
</div>
@@ -0,0 +1,413 @@
var _ = require('underscore');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var WidgetsService = require('builder/editor/widgets/widgets-service');
var WidgetDefinitionModel = require('builder/data/widget-definition-model');
var WidgetsNotifications = require('builder/widgets-notifications');
var getStylesWithoutAutostyles = require('builder/helpers/styles-without-autostyle');
var WIDGET_STYLE_PARAMS = [
'widget_style_definition',
'auto_style_definition',
'auto_style_allowed'
];
var REQUIRED_OPTS = [
'diDashboardHelpers',
'layerDefinitionsCollection',
'widgetDefinitionsCollection',
'analysisDefinitionNodesCollection'
];
/**
* Only manage **WIDGET** actions between Deep-Insights (CARTO.js) and Builder
*
*/
module.exports = {
track: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._getStylesWithoutAutostyles = getStylesWithoutAutostyles();
this._widgetDefinitionsCollection.on('add', this._onWidgetDefinitionAdded, this);
this._widgetDefinitionsCollection.on('sync', this._onWidgetDefinitionSynced, this);
this._widgetDefinitionsCollection.on('change', this._onWidgetDefinitionChanged, this);
this._widgetDefinitionsCollection.on('destroy', this._onWidgetDefinitionDestroyed, this);
this._widgetDefinitionsCollection.on('add remove reset', this._invalidateSize, this);
this._widgetDefinitionsCollection.on('setSelected', this._setSelectedWidget, this);
this._widgetDefinitionsCollection.each(this._onWidgetDefinitionAdded, this);
WidgetsNotifications.track(this._widgetDefinitionsCollection);
return this;
},
_invalidateSize: function () {
var vis = this._diDashboardHelpers.getMap();
vis.invalidateSize();
},
_onWidgetDefinitionAdded: function (model) {
var widgetModel = this._diDashboardHelpers.getWidget(model.id);
if (widgetModel) {
var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: model.get('layer_id') });
widgetModel.set({
show_source: true,
show_stats: true,
show_options: true,
table_name: layerDefModel.get('table_name')
});
this._bindWidgetChanges(widgetModel);
}
},
_onWidgetDefinitionSynced: function (model) {
var widgetModel = this._diDashboardHelpers.getWidget(model.id);
if (!widgetModel) {
this._createWidgetModel(model);
}
},
_onWidgetAutoStyleColorChanged: function (model) {
var isAutoStyleApplied = model.isAutoStyle();
var autoStyleInfo = model.getAutoStyle();
var layerId = model.layerModel.id;
var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId });
var nodeDefModel = layerDefModel && layerDefModel.getAnalysisDefinitionNodeModel();
var styleModel = layerDefModel && layerDefModel.styleModel;
var geometryType = nodeDefModel && nodeDefModel.get('simple_geom');
if (layerDefModel) {
layerDefModel.set({
autoStyle: isAutoStyleApplied ? model.id : false,
cartocss: autoStyleInfo.cartocss
});
}
if (isAutoStyleApplied && styleModel && geometryType) {
styleModel.setPropertiesFromAutoStyle({
definition: autoStyleInfo.definition,
geometryType: geometryType,
widgetId: model.id
});
}
},
_onWidgetAutoStyleChanged: function (model) {
var isAutoStyleApplied = model.isAutoStyle();
var autoStyleInfo = model.getAutoStyle();
var layerId = model.layerModel.id;
var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId });
var nodeDefModel = layerDefModel && layerDefModel.getAnalysisDefinitionNodeModel();
var styleModel = layerDefModel && layerDefModel.styleModel;
var onLayerChange = _.debounce(function () {
var dontResetStyles = true; // In order to make it more visible
model.cancelAutoStyle(dontResetStyles);
}, 10);
var stylesWithoutAutostyles = this._getStylesWithoutAutostyles(layerDefModel);
if (layerDefModel && nodeDefModel) {
if (isAutoStyleApplied) {
layerDefModel.set({
autoStyle: model.id
});
}
} else {
return;
}
if (isAutoStyleApplied) {
var geometryType = nodeDefModel.get('simple_geom');
styleModel.setPropertiesFromAutoStyle({
definition: autoStyleInfo.definition,
geometryType: geometryType,
widgetId: model.id
});
layerDefModel.set(_.extend(
{
cartocss: autoStyleInfo.cartocss,
cartocss_custom: false
},
stylesWithoutAutostyles
));
layerDefModel.once('change:autoStyle change:cartocss', onLayerChange, this);
} else {
layerDefModel.unbind('change:autoStyle change:cartocss', onLayerChange, this);
var autoStyleId = layerDefModel.get('autoStyle');
if (autoStyleId && autoStyleId === model.id) {
styleModel.resetPropertiesFromAutoStyle();
layerDefModel.set({
autoStyle: false,
cartocss_custom: layerDefModel.get('previousCartoCSSCustom'),
cartocss: layerDefModel.get('previousCartoCSS')
});
// Because we are messing with the autoStyle property on saving,
// whenever we disable autoStyle, we save the layer to force
// the sync on the cartocss
layerDefModel.save();
}
}
},
_onWidgetCustomAutoStyleColorChanged: function (model) {
var isAutoStyleApplied = model.isAutoStyle();
var autoStyleInfo = model.getAutoStyle();
var layerId = model.layerModel.id;
var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId });
var nodeDefModel = layerDefModel && layerDefModel.getAnalysisDefinitionNodeModel();
var styleModel = layerDefModel && layerDefModel.styleModel;
if (isAutoStyleApplied) {
var geometryType = nodeDefModel.get('simple_geom');
styleModel.setPropertiesFromAutoStyle({
definition: autoStyleInfo.definition,
geometryType: geometryType,
widgetId: model.id
});
layerDefModel.set({
cartocss: autoStyleInfo.cartocss,
cartocss_custom: false
}, {silent: true});
// In order to make legends aware
styleModel.trigger('style:update');
}
},
_onWidgetDefinitionChanged: function (model) {
var widgetModel = this._diDashboardHelpers.getWidget(model.id);
// Only try to update if there's a corresponding widget model
// E.g. the change of type will remove the model and provoke change events, which are not of interest (here),
// since the widget model should be re-created for the new type anyway.
if (widgetModel) {
if (model.hasChanged('type')) {
widgetModel.remove();
this._createWidgetModel(model);
} else {
var attrs = this._formatWidgetAttrs(model.changedAttributes(), model);
widgetModel.update(attrs);
}
}
var colorChanged = !model.get('widget_color_changed') &&
model.changedAttributes() &&
model.changedAttributes().widget_style_definition &&
model.changedAttributes().widget_style_definition.color;
if (colorChanged) {
model.set({ widget_color_changed: true });
}
},
_onWidgetDefinitionDestroyed: function (model) {
var widgetModel = this._diDashboardHelpers.getWidget(model.id);
if (widgetModel) {
if (widgetModel.isAutoStyle()) {
widgetModel.cancelAutoStyle();
}
this._unbindWidgetChanges(widgetModel);
widgetModel.remove();
}
},
_setSelectedWidget: function (selectedWidgetId) {
var collection = this._diDashboardHelpers.getWidgets();
collection.forEach(function (widget) {
widget.trigger('setDisabled', widget, selectedWidgetId);
});
},
_onEditWidget: function (model) {
var widgetDefinitionModel = this._widgetDefinitionsCollection.get(model.id);
if (widgetDefinitionModel) {
WidgetsService.editWidget(widgetDefinitionModel);
}
},
_onRemoveWidget: function (model) {
var widgetDefinitionModel = this._widgetDefinitionsCollection.get(model.id);
if (widgetDefinitionModel) {
WidgetsService.removeWidget(widgetDefinitionModel);
}
},
_bindWidgetChanges: function (model) {
model.bind('editWidget', this._onEditWidget, this);
model.bind('removeWidget', this._onRemoveWidget, this);
model.bind('customAutoStyle', this._onWidgetCustomAutoStyleColorChanged, this);
model.bind('change:autoStyle', this._onWidgetAutoStyleChanged, this);
model.bind('change:color', this._onWidgetAutoStyleColorChanged, this);
},
_unbindWidgetChanges: function (model) {
model.unbind('editWidget', this._onEditWidget, this);
model.unbind('removeWidget', this._onRemoveWidget, this);
model.unbind('customAutoStyle', this._onWidgetCustomAutoStyleColorChanged, this);
model.unbind('change:autoStyle', this._onWidgetAutoStyleChanged, this);
model.unbind('change:color', this._onWidgetAutoStyleColorChanged, this);
},
_createWidgetModel: function (model) {
// e.g. 'time-series' => createTimeSeriesWidget
var infix = model.get('type').replace(/(^\w|-\w)/g, function (match) {
return match.toUpperCase().replace('-', '');
});
var methodName = 'create' + infix + 'Widget';
var layerId = model.get('layer_id');
var layerModel = this._diDashboardHelpers.getLayer(layerId);
var layerDefModel = this._layerDefinitionsCollection.findWhere({ id: layerId });
var attrs = this._formatWidgetAttrs(model.attributes, model);
var widgetModel = this._diDashboardHelpers.getDashboard()[methodName](attrs, layerModel);
if (widgetModel) {
widgetModel.set({
show_source: true,
show_stats: true,
show_options: true,
table_name: layerDefModel.get('table_name')
});
this._bindWidgetChanges(widgetModel);
}
},
/**
* Massage some data points to the expected format of deep-insights API
*/
_formatWidgetAttrs: function (changedAttrs, widgetDefinitionModel) {
var formattedAttrs = changedAttrs;
// Source formatting
if (_.isString(formattedAttrs.source)) {
formattedAttrs = _.omit(formattedAttrs, 'source');
formattedAttrs.source = this._diDashboardHelpers.getAnalysisByNodeId(changedAttrs.source);
}
// Widget style or auto style changes
var thereIsWidgetStyleChange = _.find(formattedAttrs, function (value, key) {
return _.contains(WIDGET_STYLE_PARAMS, key);
});
if (!_.isUndefined(thereIsWidgetStyleChange)) {
formattedAttrs = _.omit(formattedAttrs, WIDGET_STYLE_PARAMS);
formattedAttrs.style = widgetDefinitionModel.toJSON().style;
}
return formattedAttrs;
},
manageTimeSeriesForTorque: function (model) {
function recreateWidget (currentTimeseries, newLayer, animated) {
var persistName = currentTimeseries && currentTimeseries.get('title');
// This prevents a bug if user has a range selected and switches column
newLayer.unset('customDuration');
this._createTimeseries(newLayer, animated, persistName);
}
// not a cartodb layer
if (!model.styleModel) return;
var animatedChanged = model.styleModel.changedAttributes().animated;
var previousAnimated = model.styleModel.previous('animated');
var attributeChanged;
if (animatedChanged && previousAnimated && animatedChanged.attribute !== previousAnimated.attribute) attributeChanged = animatedChanged.attribute;
var typeChanged = model.styleModel.changedAttributes().type;
var animatedAttribute = model.styleModel.get('animated') && model.styleModel.get('animated').attribute;
var previousType = model.styleModel.previous('type');
if (!typeChanged && !attributeChanged) return;
var type = model.styleModel.get('type');
var widgetModel = this._diDashboardHelpers.getWidgets().filter(function (model) {
return model.get('type') === 'time-series';
})[0];
var currentTimeseries = this._getTimeseriesDefinition();
var newLayer = this._diDashboardHelpers.getLayer(model.id);
if (type !== 'animation' && previousType === 'animation' && this._lastType !== type) {
if (widgetModel) {
this._removeTimeseries();
}
this._lastType = type;
this._lastTSAnimateChange = '';
}
if (type === 'animation' && (this._lastTSAnimateChange !== attributeChanged || this._lastType !== 'animation')) {
if (widgetModel) {
this._removeTimeseries();
}
if (newLayer.get('type') === 'torque' || model.get('type') === 'torque') {
recreateWidget.call(this, currentTimeseries, newLayer, _.extend({ animated: true }, animatedChanged, { attribute: animatedAttribute }));
}
this._lastType = type;
this._lastTSAnimateChange = attributeChanged;
}
},
_removeTimeseries: function () {
this._widgetDefinitionsCollection.models.forEach(function (def) {
if (def.get('type') === 'time-series') {
def.set({avoidNotification: true}, {silent: true});
def.destroy();
}
});
},
_getTimeseriesDefinition: function () {
return this._widgetDefinitionsCollection.findWhere({type: 'time-series'});
},
_createTimeseries: function (newLayer, animatedChanged, persist) {
function getColumnType (sourceId, columnName) {
var node = this._analysisDefinitionNodesCollection.get(sourceId);
return node && node.querySchemaModel.getColumnType(columnName);
}
this._removeTimeseries();
var attribute = animatedChanged && animatedChanged.attribute || '';
var animated = animatedChanged && animatedChanged.animated;
var sourceId = newLayer.get('source');
var source = this._diDashboardHelpers.getAnalysisByNodeId(sourceId);
if (attribute) {
var baseAttrs = {
type: 'time-series',
layer_id: newLayer.get('id'),
source: source,
options: {
column: attribute,
title: persist || 'time_date__t',
animated: animated
},
style: {
widget_style: WidgetDefinitionModel.getDefaultWidgetStyle('time-series')
}
};
var columnType = getColumnType.call(this, sourceId, attribute);
if (columnType !== 'date') {
baseAttrs.options.bins = 256;
}
this._widgetDefinitionsCollection.create(baseAttrs, { wait: true });
}
}
};