Initial commit
This commit is contained in:
@@ -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;
|
||||
+214
@@ -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;
|
||||
+170
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+48
@@ -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);
|
||||
});
|
||||
};
|
||||
+22
@@ -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);
|
||||
}
|
||||
};
|
||||
+27
@@ -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 });
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user