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,29 @@
<div class="u-flex u-justifyCenter">
<div class="Modal-inner Modal-inner--grid u-flex u-justifyCenter">
<div class="Modal-icon">
<svg width="24px" height="25px" viewbox="521 436 24 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M524.5,440 L540.5,440 L540.5,460 L524.5,460 L524.5,440 Z M528.5,437 L536.5,437 L536.5,440 L528.5,440 L528.5,437 Z M522,440 L544,440 L522,440 Z M528.5,443.5 L528.5,455.5 L528.5,443.5 Z M532.5,443.5 L532.5,455.5 L532.5,443.5 Z M536.5,443.5 L536.5,455.5 L536.5,443.5 Z" id="Shape" stroke="#F19243" stroke-width="1" fill="none"/>
</svg>
</div>
<div>
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--xl"><%- _t('components.modals.edit-feature.delete.title') %></h2>
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('components.modals.edit-feature.delete.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.delete.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.delete.confirm') %>
</span>
</button>
</li>
</ul>
</div>
</div>
</div>
@@ -0,0 +1,84 @@
var CoreView = require('backbone/core-view');
var template = require('./edit-feature-action.tpl');
/**
* View representing the apply button for a form
*/
module.exports = CoreView.extend({
events: {
'click .js-save': '_onSaveClicked'
},
initialize: function (opts) {
if (!opts.featureModel) throw new Error('featureModel is required');
if (!opts.geometryFormModel) throw new Error('geometryFormModel is required');
if (!opts.attributesFormModel) throw new Error('attributesFormModel is required');
this._featureModel = opts.featureModel;
this._geometryFormModel = opts.geometryFormModel;
this._attributesFormModel = opts.attributesFormModel;
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.html(template({
label: this._featureModel.isNew() ? _t('editor.edit-feature.add') : _t('editor.edit-feature.save'),
isDisabled: !this._canSave()
}));
return this;
},
_initBinds: function () {
this.model.on('change', this.render, this);
this._featureModel.on('change', function () {
this.model.set('hasChanges', true);
}, this);
this.add_related_model(this._featureModel);
this._geometryFormModel.bind('validate', function (isValid) {
this.model.set('isValidGeometry', !isValid);
}, this);
this.add_related_model(this._geometryFormModel);
this._attributesFormModel.bind('validate', function (isValid) {
this.model.set('isValidAttributes', !isValid);
}, this);
this.add_related_model(this._attributesFormModel);
},
_canSave: function () {
var hasChanges = this.model.get('hasChanges');
var isValidGeometry = this.model.get('isValidGeometry');
var isValidAttributes = this.model.get('isValidAttributes');
return isValidGeometry && isValidAttributes && hasChanges;
},
_onSaveClicked: function () {
if (this._canSave()) {
this._saveFeature();
}
},
_saveFeature: function () {
var self = this;
var operation = this._featureModel.isNew() ? 'add' : 'save';
var event = operation + 'Feature';
this._featureModel.trigger(event);
this._featureModel.save({
success: function () {
self._featureModel.trigger('saveFeatureSuccess', operation, self._featureModel);
self.model.set('hasChanges', false);
},
error: function () {
self._featureModel.trigger('saveFeatureFailed');
}
});
}
});
@@ -0,0 +1,5 @@
<button class="CDB-Button CDB-Button--primary js-save <% if (isDisabled) { %>is-disabled<% } %>">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">
<%- label %>
</span>
</button>
@@ -0,0 +1,135 @@
var _ = require('underscore');
var Backbone = require('backbone');
var ColumnRowData = require('builder/data/column-row-data');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var FIELDTYPE_TO_FORMTYPE = {
string: 'Text',
boolean: 'Radio',
number: 'Number',
date: 'DateTime',
geometry: 'Text'
};
var BLACKLISTED_COLUMNS = ['created_at', 'the_geom', 'the_geom_webmercator', 'updated_at'];
var REQUIRED_OPTS = [
'featureModel',
'columnsCollection',
'configModel',
'nodeDefModel'
];
module.exports = Backbone.Model.extend({
initialize: function (attrs, opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._generateSchema();
this._initBinds();
},
_initBinds: function () {
this.bind('change', this._onChange, this);
},
_onChange: function () {
_.each(this.changed, function (val, key) {
this._featureModel.set(key, val);
}, this);
},
_getInputType: function (columnRowData) {
var rows = columnRowData.getRows();
return rows && rows.length ? 'Suggest' : 'Text';
},
_getSupportedColumnType: function (columnType) {
var supportedColumnType = _.find(_.keys(FIELDTYPE_TO_FORMTYPE), function (fieldType) {
return fieldType === columnType;
});
return supportedColumnType || 'string';
},
_generateSchema: function () {
var self = this;
this.schema = {};
this._columnsCollection.each(function (model) {
var columnName = model.get('name');
var columnType = this._getSupportedColumnType(model.get('type'));
if (!_.contains(BLACKLISTED_COLUMNS, columnName)) {
// default field type
this.schema[columnName] = {
type: FIELDTYPE_TO_FORMTYPE[columnType]
};
if (columnType === 'date') {
this.schema[columnName].dialogMode = DialogConstants.Mode.FLOAT;
}
if (columnType === 'string') {
var columnRowData = new ColumnRowData({
column: columnName
}, {
nodeDefModel: this._nodeDefModel,
configModel: this._configModel
});
columnRowData.bind('columnsFetched', function (rows) {
var type = self._getInputType(this);
if (self.schema[columnName].type !== type) {
self.schema[columnName].type = type;
if (type === 'Suggest') {
self.schema[columnName].dialogMode = DialogConstants.Mode.FLOAT;
}
self.schema[columnName].editorAttrs = {
showSearch: true,
defaultValue: true,
allowFreeTextInput: true,
collectionData: rows
};
self._onChangeSchema();
}
});
columnRowData.fetch();
}
if (columnType === 'number') {
this.schema[columnName].isFormatted = true;
this.schema[columnName].showSlider = false;
}
if (columnType === 'boolean') {
this.schema[columnName].options = [
{
label: _t('form-components.editors.radio.true'),
val: true
}, {
label: _t('form-components.editors.radio.false'),
val: false
}, {
label: _t('form-components.editors.radio.null'),
val: null
}
];
}
if (columnName === 'cartodb_id') {
this.schema[columnName].isFormatted = false;
this.schema[columnName].editorAttrs = {
disabled: true
};
}
}
}, this);
},
_onChangeSchema: function () {
this.trigger('changeSchema');
}
});
@@ -0,0 +1,48 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var template = require('./edit-feature-attributes-form.tpl');
module.exports = CoreView.extend({
module: 'editor:layers:edit-feature-content-views:edit-feature-attributes-form-view',
render: function () {
this.clearSubViews();
this.$el.html(template());
this._generateForms();
this.model.bind('changeSchema', this._generateForms, this);
return this;
},
_generateForms: function () {
var self = this;
if (this._formView) {
this._formView.remove();
}
this._formView = new Backbone.Form({
model: this.model
});
this._formView.bind('change', function () {
var validate = this.validate();
self.model.trigger('validate', !!validate);
if (!validate) {
this.commit();
}
});
this.$('.js-form').append(this._formView.render().$el);
},
clean: function () {
if (this._formView) {
this._formView.remove();
}
}
});
@@ -0,0 +1,11 @@
<div class="Editor-HeaderInfo">
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
<div class="Editor-HeaderInfo-inner CDB-Text">
<div class="Editor-HeaderInfo-title u-bSpace--m">
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.edit-feature.attributes') %></h2>
</div>
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.edit-feature.attributes-columns') %></p>
<div class="js-form"></div>
</div>
</div>
@@ -0,0 +1,12 @@
var CoreView = require('backbone/core-view');
var template = require('./edit-feature-control.tpl');
module.exports = CoreView.extend({
render: function () {
this.$el.html(template());
return this;
}
});
@@ -0,0 +1,3 @@
<button class="CDB-Button u-upperCase js-back">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-actionTextColor"><%- _t('editor.edit-feature.cancel') %></span>
</button>
@@ -0,0 +1,96 @@
var _ = require('underscore');
var Backbone = require('backbone');
var cdb = require('internal-carto.js');
module.exports = Backbone.Model.extend({
_COORDINATES_OPTIONS: {
min_lat: -90,
max_lat: 90,
min_lng: -180,
max_lng: 180
},
initialize: function (attrs, opts) {
if (!opts.featureModel) throw new Error('featureModel is required');
this._featureModel = opts.featureModel;
this._generateSchema();
},
_formatCoordinate: function (coordinate) {
if (this._isValidCoordinate(coordinate)) {
return parseFloat(parseFloat(coordinate).toFixed(8));
}
},
_isValidCoordinate: function (coordinate) {
return _.isNumber(coordinate) || _.isString(coordinate);
},
_getLatitude: function (pos) {
return pos && pos[0];
},
_getLongitude: function (pos) {
return pos && pos[1];
},
_validateGeom: function (value) {
var validation;
var coordinates;
var geoJSON = JSON.parse(value);
if (this._featureModel.isLine()) {
coordinates = cdb.helpers.GeoJSONHelper.getPolylineLatLngsFromGeoJSONCoords(geoJSON);
} else {
coordinates = cdb.helpers.GeoJSONHelper.getPolygonLatLngsFromGeoJSONCoords(geoJSON);
}
var errors = _.chain(coordinates)
.reduce(function (memo, coordinate) {
var latitude = this._formatCoordinate(this._getLatitude(coordinate));
var longitude = this._formatCoordinate(this._getLongitude(coordinate));
var latError = (latitude > this._COORDINATES_OPTIONS.max_lat || latitude < this._COORDINATES_OPTIONS.min_lat);
if (latError) {
memo.push(_t('editor.edit-feature.out-of-bounds-lat'));
}
var lngError = (longitude > this._COORDINATES_OPTIONS.max_lng || longitude < this._COORDINATES_OPTIONS.min_lng);
if (lngError) {
memo.push(_t('editor.edit-feature.out-of-bounds-lng'));
}
return memo;
}, [], this)
.uniq()
.value();
if (errors.length) {
validation = {
message: errors.join(' ')
};
}
return validation;
},
_generateSchema: function () {
this.schema = {};
this.schema.the_geom = {
type: 'Text',
validators: ['required', this._validateGeom.bind(this)],
editorAttrs: {
disabled: true
},
hasCopyButton: this._featureModel.has('the_geom')
};
}
});
@@ -0,0 +1,53 @@
var Backbone = require('backbone');
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var template = require('./edit-feature-geometry-form.tpl');
module.exports = CoreView.extend({
render: function () {
this.clearSubViews();
this.$el.html(template());
this._generateForms();
return this;
},
_generateForms: function () {
if (this._formView) {
this._formView.remove();
}
this._formView = new Backbone.Form({
model: this.model
});
this._formView.bind('change', this._validateForm, this);
this.model.bind('updateFeature', function (attrs) {
_.each(attrs, function (value, key) {
this._formView.fields[key].editor.setValue(value);
}, this);
this._validateForm();
}, this);
this.$('.js-form').append(this._formView.render().$el);
},
_validateForm: function () {
var validate = this._formView.validate();
this.model.trigger('validate', !!validate);
if (!validate) {
this._formView.commit();
}
},
clean: function () {
if (this._formView) {
this._formView.remove();
}
}
});
@@ -0,0 +1,10 @@
<div class="Editor-HeaderInfo">
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
<div class="Editor-HeaderInfo-inner CDB-Text">
<div class="Editor-HeaderInfo-title u-bSpace--m">
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.edit-feature.geometry') %></h2>
</div>
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.edit-feature.geometry-edit') %></p>
<div class="js-form"></div>
</div>
</div>
@@ -0,0 +1,130 @@
var _ = require('underscore');
var EditFeatureGeometryFormModel = require('./edit-feature-geometry-form-model');
var getCoordinatesFromGeoJSON = function (geoJSON) {
if (geoJSON && geoJSON.coordinates && geoJSON.coordinates.length === 2) {
return geoJSON.coordinates;
}
};
module.exports = EditFeatureGeometryFormModel.extend({
initialize: function () {
EditFeatureGeometryFormModel.prototype.initialize.apply(this, arguments);
this._initBinds();
},
_initBinds: function () {
this.bind('change:lat', this._onLatLngChange, this);
this.bind('change:lng', this._onLatLngChange, this);
},
_onLatLngChange: function () {
var newGeoJSON = this._toGeoJSON();
var currentGeoJSON = {};
if (this._featureModel.get('the_geom')) {
currentGeoJSON = JSON.parse(this._featureModel.get('the_geom'));
}
// Only set `the_geom` if there's new geoJSON
if (!_.isEqual(newGeoJSON, currentGeoJSON)) {
this._featureModel.set('the_geom', JSON.stringify(newGeoJSON));
}
},
_toGeoJSON: function () {
var latitude = this._formatCoordinate(this._getLatitude());
var longitude = this._formatCoordinate(this._getLongitude());
if (this._isValidCoordinate(latitude) && this._isValidCoordinate(longitude)) {
return {
'type': 'Point',
'coordinates': [ longitude, latitude ]
};
}
},
_getLatitude: function () {
if (this.has('lat')) {
return this.get('lat');
}
var geoJSON = JSON.parse(this._featureModel.get('the_geom'));
var geoJSONCoordinates = getCoordinatesFromGeoJSON(geoJSON);
var latitudeFromGeoJSON = geoJSONCoordinates && geoJSONCoordinates[1];
if (latitudeFromGeoJSON) {
return latitudeFromGeoJSON;
}
},
_getLongitude: function () {
if (this.has('lng')) {
return this.get('lng');
}
var geoJSON = JSON.parse(this._featureModel.get('the_geom'));
var geoJSONCoordinates = getCoordinatesFromGeoJSON(geoJSON);
var longitudeFromGeoJSON = geoJSONCoordinates && geoJSONCoordinates[1];
if (longitudeFromGeoJSON) {
return longitudeFromGeoJSON;
}
},
_validateLatitude: function (value, formValues) {
var numericValue = +value;
var error = {
message: _t('editor.edit-feature.valid-lat')
};
if (_.isNumber(numericValue) &&
(numericValue >= this._COORDINATES_OPTIONS.min_lat && numericValue <= this._COORDINATES_OPTIONS.max_lat)) {
return null; // valid latitude
}
if (_.isNumber(numericValue) &&
(numericValue > this._COORDINATES_OPTIONS.max_lat || numericValue < this._COORDINATES_OPTIONS.min_lat)) {
error.message = _t('editor.edit-feature.out-of-bounds-lat');
}
return error;
},
_validateLongitude: function (value, formValues) {
var numericValue = +value;
var error = {
message: _t('editor.edit-feature.valid-lng')
};
if (_.isNumber(numericValue) &&
(numericValue >= this._COORDINATES_OPTIONS.min_lng && numericValue <= this._COORDINATES_OPTIONS.max_lng)) {
return null; // valid longitude
}
if (_.isNumber(numericValue) &&
(numericValue > this._COORDINATES_OPTIONS.max_lng || numericValue < this._COORDINATES_OPTIONS.min_lng)) {
error.message = _t('editor.edit-feature.out-of-bounds-lng');
}
return error;
},
_generateSchema: function () {
this.schema = {};
this.schema.lng = {
type: 'Number',
validators: ['required', this._validateLongitude.bind(this)],
showSlider: false
};
this.schema.lat = {
type: 'Number',
validators: ['required', this._validateLatitude.bind(this)],
showSlider: false
};
}
});
@@ -0,0 +1,87 @@
var CoreView = require('backbone/core-view');
var template = require('./edit-feature-header.tpl');
var ContextMenuFactory = require('builder/components/context-menu-factory-view');
var ConfirmationView = require('builder/components/modals/confirmation/modal-confirmation-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var templateConfirmation = require('./delete-feature-confirmation.tpl');
var REQUIRED_OPTS = [
'url',
'tableName',
'modals',
'isNew',
'layerDefinitionModel'
];
module.exports = CoreView.extend({
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
},
render: function () {
this.clearSubViews();
var letter = this._layerDefinitionModel.get('letter');
var featureType = this.model.getFeatureType() ? _t('editor.edit-feature.features.' + this.model.getFeatureType()) : _t('editor.edit-feature.features.geometry');
var breadcrumbLabel = this._isNew ? _t('editor.edit-feature.add-' + featureType) : _t('editor.edit-feature.edit', { featureType: featureType });
this.$el.html(
template({
url: this._url,
layerName: this._layerDefinitionModel.getName(),
tableName: this._tableName,
bgColor: this._layerDefinitionModel.getColor(),
letter: letter,
featureType: featureType,
breadcrumbLabel: breadcrumbLabel
})
);
if (!this._isNew) {
this._initViews();
}
return this;
},
_initViews: function () {
var menuItems = [{
label: _t('editor.edit-feature.delete', { featureType: _t('editor.edit-feature.features.' + this.model.getFeatureType()) }),
val: 'delete-feature',
destructive: true,
action: this._confirmDeleteFeature.bind(this)
}];
this._contextMenuFactory = new ContextMenuFactory({
menuItems: menuItems
});
this.$('.js-context-menu').append(this._contextMenuFactory.render().el);
this.addView(this._contextMenuFactory);
},
_confirmDeleteFeature: function () {
this._modals.create(function (modalModel) {
return new ConfirmationView({
modalModel: modalModel,
template: templateConfirmation,
runAction: this._destroyFeature.bind(this)
});
}.bind(this));
},
_destroyFeature: function () {
this.model.trigger('destroyFeature');
this.model.destroy({
success: function () {
this.model.trigger('destroyFeatureSuccess');
}.bind(this),
error: function () {
this.model.trigger('destroyFeatureFailed');
}.bind(this)
});
}
});
@@ -0,0 +1,38 @@
<ul class="Editor-breadcrumb">
<li class="Editor-breadcrumbItem CDB-Text CDB-Size-medium u-actionTextColor">
<button class="js-back">
<i class="CDB-IconFont CDB-IconFont-arrowPrev Size-large u-rSpace"></i>
<span class="Editor-breadcrumbLink"><%- _t('back') %></span>
</button>
</li>
<li class="Editor-breadcrumbItem CDB-Text CDB-Size-medium">
<span class="Editor-breadcrumbSep"> / </span>
<%- breadcrumbLabel %>
</li>
</ul>
<div class="Editor-HeaderInfoEditor Editor-HeaderInfoEditor--layer">
<div class="Editor-HeaderInfo-inner Editor-HeaderInfo-inner--wide u-ellipsis">
<div class="Editor-HeaderInfo-title u-bSpace">
<span class="CDB-SelectorLayer-letter CDB-Text CDB-Size-small u-whiteTextColor u-tSpace--m u-rSpace--m u-upperCase" style="background-color: <%- bgColor %>;">
<%- letter %>
</span>
<h2 class="Inline-editor">
<div class="CDB-Text CDB-Size-huge is-light u-ellipsis">
<%- layerName %>
</div>
</h2>
</div>
<div class="Editor-HeaderInfo-source u-flex">
<p class="CDB-Text CDB-Size-small u-ellipsis">
<a href="<%- url %>" target="_blank" title="<%- tableName %>" class="Editor-headerLayerName"><%- tableName %></a>
</p>
</div>
</div>
<div class="u-flex u-tSpace-xl js-context-menu"></div>
</div>
@@ -0,0 +1,47 @@
var CoreView = require('backbone/core-view');
var EditFeatureGeometryFormView = require('./edit-feature-geometry-form-view');
var EditFeatureAttributesFormView = require('./edit-feature-attributes-form-view');
module.exports = CoreView.extend({
initialize: function (opts) {
if (!opts.featureModel) throw new Error('featureModel is required');
if (!opts.geometryFormModel) throw new Error('geometryFormModel is required');
if (!opts.attributesFormModel) throw new Error('attributesFormModel is required');
this._featureModel = opts.featureModel;
this._geometryFormModel = opts.geometryFormModel;
this._attributesFormModel = opts.attributesFormModel;
},
render: function () {
if (this._editFeatureGeometryFormView) {
this._editFeatureGeometryFormView.clean();
}
if (this._editFeatureAttributesFormView) {
this._editFeatureAttributesFormView.clean();
}
this.clearSubViews();
this.$el.empty();
this._editFeatureGeometryFormView = new EditFeatureGeometryFormView({
model: this._geometryFormModel
});
this.addView(this._editFeatureGeometryFormView);
this.$el.append(this._editFeatureGeometryFormView.render().el);
this._editFeatureAttributesFormView = new EditFeatureAttributesFormView({
model: this._attributesFormModel
});
this.addView(this._editFeatureAttributesFormView);
this.$el.append(this._editFeatureAttributesFormView.render().el);
return this;
}
});