Initial commit
@@ -0,0 +1,19 @@
|
||||
<div class="Modal">
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-headerContainer">
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-bSpace"><%- _t('components.modals.add-analysis.modal-title') %></h2>
|
||||
<h3 class="CDB-Text CDB-Size-medium u-secondaryTextColor"><%- _t('components.modals.add-analysis.modal-desc') %></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-container Modal-container--analysis">
|
||||
<div class="Modal-inner js-body">
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<div class="Modal-footerContainer u-flex u-justifyEnd">
|
||||
<button class="CDB-Button CDB-Button--primary CDB-Button--big is-disabled js-add">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-analysis.add-btn') %></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,106 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var AnalysisOptionsCollection = require('./analysis-options-collection');
|
||||
var analysisOptions = require('./analysis-options');
|
||||
var AnalysisViewPane = require('./analysis-view-pane');
|
||||
var renderLoading = require('builder/components/loading/render-loading');
|
||||
var DataServicesApiCheck = require('builder/editor/layers/layer-content-views/analyses/analyses-quota/analyses-quota-info');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'modalModel',
|
||||
'configModel',
|
||||
'userModel',
|
||||
'layerDefinitionModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* View to add a new analysis node.
|
||||
* Expected to be rendered in a modal.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Dialog-content Dialog-content--expanded',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._analysisDefinitionNodeModel = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();
|
||||
|
||||
this._queryGeometryModel = this._analysisDefinitionNodeModel.queryGeometryModel;
|
||||
this.listenTo(this._queryGeometryModel, 'change', this.render);
|
||||
|
||||
this._analysisOptions = analysisOptions({
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel,
|
||||
queryGeometryModel: this._queryGeometryModel
|
||||
});
|
||||
this._analysisOptionsCollection = new AnalysisOptionsCollection();
|
||||
|
||||
this._dataservicesApiHealth = DataServicesApiCheck.get();
|
||||
|
||||
if (!this._isFetchingGeometry()) {
|
||||
this._queryGeometryModel.fetch();
|
||||
}
|
||||
|
||||
this._initOptions();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var render = this.render.bind(this);
|
||||
var dsApiNeedsCheck = this._dataservicesApiHealth.needsCheck();
|
||||
if (dsApiNeedsCheck) {
|
||||
this._dataservicesApiHealth.fetch({
|
||||
success: render
|
||||
});
|
||||
}
|
||||
|
||||
if (this._isFetchingGeometry() || dsApiNeedsCheck) {
|
||||
this._renderLoadingView();
|
||||
} else {
|
||||
this._renderStackView();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderLoadingView: function () {
|
||||
this.$el.html(
|
||||
renderLoading({
|
||||
title: _t('components.modals.add-widgets.loading-title')
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_renderStackView: function () {
|
||||
var view = new AnalysisViewPane({
|
||||
modalModel: this._modalModel,
|
||||
analysisOptions: this._analysisOptions,
|
||||
analysisOptionsCollection: this._analysisOptionsCollection,
|
||||
layerDefinitionModel: this._layerDefinitionModel,
|
||||
queryGeometryModel: this._queryGeometryModel
|
||||
});
|
||||
|
||||
this.$el.html(view.render().$el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_isFetchingGeometry: function () {
|
||||
return this._queryGeometryModel.get('status') === 'fetching';
|
||||
},
|
||||
|
||||
_initOptions: function () {
|
||||
// Flatten the options hierarchy to a flat array structure that's easier to handle programmatically.
|
||||
var modelsAttrs = _.reduce(Object.keys(this._analysisOptions), function (memo, category) {
|
||||
var categoryDef = this._analysisOptions[category];
|
||||
categoryDef.analyses.forEach(function (d) {
|
||||
memo.push(_.extend({}, d, { category: category, type: d.nodeAttrs.type }));
|
||||
});
|
||||
|
||||
return memo;
|
||||
}, [], this);
|
||||
|
||||
this._analysisOptionsCollection.reset(modelsAttrs);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
var _ = require('underscore');
|
||||
var AnalysisCategoryView = require('./analysis-category-pane-view');
|
||||
|
||||
module.exports = function (analysisOptions) {
|
||||
return [{
|
||||
type: 'create_clean',
|
||||
createTabPaneItem: function (optionsCollection, opts) {
|
||||
return {
|
||||
label: _t('analysis-category.create-clean'),
|
||||
name: 'create_clean',
|
||||
createContentView: function () {
|
||||
return new AnalysisCategoryView(_.extend({
|
||||
analysisOptions: analysisOptions,
|
||||
analysisType: 'create_clean',
|
||||
collection: optionsCollection
|
||||
}, opts));
|
||||
}
|
||||
};
|
||||
}
|
||||
}, {
|
||||
type: 'analyze_predict',
|
||||
createTabPaneItem: function (optionsCollection, opts) {
|
||||
return {
|
||||
label: _t('analysis-category.analyze-predict'),
|
||||
name: 'analyze_predict',
|
||||
createContentView: function () {
|
||||
return new AnalysisCategoryView(_.extend({
|
||||
analysisOptions: analysisOptions,
|
||||
analysisType: 'analyze_predict',
|
||||
collection: optionsCollection
|
||||
}, opts));
|
||||
}
|
||||
};
|
||||
}
|
||||
}, {
|
||||
type: 'data_transformation',
|
||||
createTabPaneItem: function (optionsCollection, opts) {
|
||||
return {
|
||||
label: _t('analysis-category.data-transformation'),
|
||||
name: 'data_transformation',
|
||||
createContentView: function () {
|
||||
return new AnalysisCategoryView(_.extend({
|
||||
analysisOptions: analysisOptions,
|
||||
analysisType: 'data_transformation',
|
||||
collection: optionsCollection
|
||||
}, opts));
|
||||
}
|
||||
};
|
||||
}
|
||||
}];
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var AnalysisCategoryPane = require('./analysis-category-pane');
|
||||
|
||||
/**
|
||||
* View to add a new analysis node.
|
||||
* Expected to be rendered in a modal.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Dialog-content Dialog-content--expanded',
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.modalModel) throw new Error('modalModel is required');
|
||||
if (!opts.analysisOptionsCollection) throw new Error('analysisOptionsCollection is required');
|
||||
if (!opts.analysisType) throw new Error('analysisType is required');
|
||||
if (!opts.analysisOptions) throw new Error('analysisOptions is required');
|
||||
if (!opts.queryGeometryModel) throw new Error('queryGeometryModel is required');
|
||||
|
||||
this._modalModel = opts.modalModel;
|
||||
this._queryGeometryModel = opts.queryGeometryModel;
|
||||
this._analysisOptionsCollection = opts.analysisOptionsCollection;
|
||||
this._analysisType = opts.analysisType;
|
||||
this._analysisOptions = opts.analysisOptions;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var view = new ScrollView({
|
||||
createContentView: function () {
|
||||
return new AnalysisCategoryPane({
|
||||
collection: this._analysisOptionsCollection,
|
||||
title: this.options.categoryTitle || this.options.category,
|
||||
category: this._analysisType === 'all' ? null : this._analysisType,
|
||||
categoryTitle: this._analysisType === 'all' ? null : this._analysisOptions[this._analysisType].label,
|
||||
simpleGeometryType: this._queryGeometryModel.get('simple_geom')
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var AnalysisOptionView = require('./analysis-option-view');
|
||||
var template = require('./analysis-category.tpl');
|
||||
|
||||
/**
|
||||
* View to render an individual analysis category and its analysis options
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Modal-analysisContainer',
|
||||
|
||||
options: {
|
||||
category: '',
|
||||
categoryTitle: '',
|
||||
simpleGeometryType: ''
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(this._html());
|
||||
this.collection.each(this._renderOption, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_html: function () {
|
||||
return template({
|
||||
title: this.options.title
|
||||
});
|
||||
},
|
||||
|
||||
_renderOption: function (analysisOptionModel) {
|
||||
if (this.options.category && !analysisOptionModel.belongsTo(this.options.category)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var view = new AnalysisOptionView({
|
||||
model: analysisOptionModel,
|
||||
simpleGeometryTypeInput: this.options.simpleGeometryType
|
||||
});
|
||||
|
||||
this.addView(view);
|
||||
this._$list().append(view.render().el);
|
||||
},
|
||||
|
||||
_$list: function () {
|
||||
return this.$('.js-analyses-list');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<ul class="ModalBlockList js-analyses-list"></ul>
|
||||
@@ -0,0 +1,51 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var camshaftReference = require('builder/data/camshaft-reference');
|
||||
var nodeIds = require('builder/value-objects/analysis-node-ids');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
title: '',
|
||||
category: '',
|
||||
selected: false,
|
||||
type_group: '',
|
||||
desc: '',
|
||||
link: ''
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.nodeAttrs) throw new Error('nodeAttrs is required');
|
||||
|
||||
this._nodeAttrs = opts.nodeAttrs;
|
||||
},
|
||||
|
||||
belongsTo: function (category) {
|
||||
return this.get('category') === category;
|
||||
},
|
||||
|
||||
acceptsGeometryTypeAsInput: function (simpleGeometryType) {
|
||||
if (this.get('dummy') === true) return true;
|
||||
return camshaftReference.isValidInputGeometryForType(simpleGeometryType, this._nodeAttrs.type);
|
||||
},
|
||||
|
||||
getValidInputGeometries: function () {
|
||||
return camshaftReference.getValidInputGeometriesForType(this._nodeAttrs.type);
|
||||
},
|
||||
|
||||
getFormAttrs: function (layerDefModel) {
|
||||
var letter = layerDefModel.get('letter');
|
||||
var sourceId = layerDefModel.get('source');
|
||||
|
||||
return _.extend(
|
||||
{
|
||||
source: sourceId,
|
||||
id: letter === nodeIds.letter(sourceId)
|
||||
? nodeIds.next(sourceId)
|
||||
: letter + '1'
|
||||
},
|
||||
this._nodeAttrs
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
var _ = require('underscore');
|
||||
var AnalysisOptionModel = require('./analysis-option-model');
|
||||
|
||||
/**
|
||||
* Custom model for deprecated SQL function type, to set correct primary source
|
||||
*/
|
||||
module.exports = AnalysisOptionModel.extend({
|
||||
|
||||
/**
|
||||
* @override {AnalysisOptionModel.getNodeAttrs}
|
||||
*/
|
||||
getFormAttrs: function (layerDefModel) {
|
||||
var attrs = AnalysisOptionModel.prototype.getFormAttrs.apply(this, arguments);
|
||||
delete attrs.source;
|
||||
|
||||
_.extend(attrs, {
|
||||
primary_source: layerDefModel.get('source')
|
||||
});
|
||||
|
||||
return attrs;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
var _ = require('underscore');
|
||||
var AnalysisOptionModel = require('./analysis-option-model');
|
||||
var camshaftReference = require('builder/data/camshaft-reference');
|
||||
|
||||
/**
|
||||
* Model to represent a generated analysis option.
|
||||
*/
|
||||
module.exports = AnalysisOptionModel.extend({
|
||||
|
||||
/**
|
||||
* @override {AnalysisOptionModel.getNodeAttrs}
|
||||
*/
|
||||
getFormAttrs: function () {
|
||||
var attrs = AnalysisOptionModel.prototype.getFormAttrs.apply(this, arguments);
|
||||
|
||||
this._removeSourceIfThereAreMultipleSources(attrs);
|
||||
|
||||
return attrs;
|
||||
},
|
||||
|
||||
_removeSourceIfThereAreMultipleSources: function (attrs) {
|
||||
var params = camshaftReference.paramsForType(attrs.type);
|
||||
var sourceCount = _.reduce(Object.keys(params), function (memo, name) {
|
||||
if (params[name].type === 'node') {
|
||||
memo++;
|
||||
}
|
||||
return memo;
|
||||
}, 0);
|
||||
|
||||
if (sourceCount > 1) {
|
||||
delete attrs.source;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
var _ = require('underscore');
|
||||
var AnalysisOptionModel = require('./analysis-option-model');
|
||||
|
||||
/**
|
||||
* Custom model for merge type, to set correct primary source
|
||||
*/
|
||||
module.exports = AnalysisOptionModel.extend({
|
||||
|
||||
/**
|
||||
* @override {AnalysisOptionModel.getNodeAttrs}
|
||||
*/
|
||||
getFormAttrs: function (layerDefModel) {
|
||||
var attrs = AnalysisOptionModel.prototype.getFormAttrs.apply(this, arguments);
|
||||
delete attrs.source;
|
||||
|
||||
_.extend(attrs, {
|
||||
primary_source_name: 'left_source',
|
||||
left_source: layerDefModel.get('source')
|
||||
});
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./analysis-option.tpl');
|
||||
var Analyses = require('builder/data/analyses');
|
||||
|
||||
/**
|
||||
* View for an individual analysis option.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'li',
|
||||
className: 'ModalBlockList-item',
|
||||
|
||||
events: {
|
||||
'mouseenter': '_onMouseEnter',
|
||||
'mouseleave': '_onMouseLeave',
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
this._simpleGeometryTypeInput = opts.simpleGeometryTypeInput; // might be undefined/null, so don't check
|
||||
|
||||
this.listenTo(this.model, 'change:selected', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var props = this.model.pick('title', 'type_group', 'desc', 'selected', 'link');
|
||||
|
||||
props.enabled = this._acceptsInputGeometry();
|
||||
|
||||
if (!props.enabled) {
|
||||
props.desc = this._disabledDesc();
|
||||
}
|
||||
|
||||
var analysisType = this.model.get('type');
|
||||
var analysisItem = Analyses.getAnalysisByType(analysisType);
|
||||
var animationTemplate = analysisItem && analysisItem.animationTemplate;
|
||||
var genericType = '';
|
||||
|
||||
if (animationTemplate) {
|
||||
genericType = analysisItem.genericType || analysisType;
|
||||
}
|
||||
|
||||
this.$el.html(template(_.extend(props, { type: genericType })));
|
||||
|
||||
if (animationTemplate) {
|
||||
this.$('.js-animation').append(animationTemplate);
|
||||
}
|
||||
|
||||
this.$el.toggleClass('is-selected', this.model.get('selected'));
|
||||
this.$el.toggleClass('is-disabled', !props.enabled);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onMouseEnter: function () {
|
||||
if (this._acceptsInputGeometry()) {
|
||||
this.$('.js-animation').addClass('has-autoplay');
|
||||
}
|
||||
},
|
||||
|
||||
_onMouseLeave: function () {
|
||||
if (this._acceptsInputGeometry()) {
|
||||
this.$('.js-animation').removeClass('has-autoplay');
|
||||
}
|
||||
},
|
||||
|
||||
_onClick: function (event) {
|
||||
var linkClicked = event && event.target && $(event.target).hasClass('js-more');
|
||||
if (!linkClicked && this._acceptsInputGeometry()) {
|
||||
this.model.set('selected', true);
|
||||
}
|
||||
},
|
||||
|
||||
_disabledDesc: function (desc) {
|
||||
return _t('components.modals.add-analysis.disabled-option-desc', {
|
||||
simpleGeometryType: this._simpleGeometryTypeInput || _t('components.modals.add-analysis.unknown-geometry-type'),
|
||||
requiredInputGeometries: this._getRequiredInputGeometries()
|
||||
});
|
||||
},
|
||||
|
||||
_getRequiredInputGeometries: function () {
|
||||
return _t('components.modals.add-analysis.geometry-types.' + this.model.getValidInputGeometries());
|
||||
},
|
||||
|
||||
_acceptsInputGeometry: function () {
|
||||
return this.model.acceptsGeometryTypeAsInput(this._simpleGeometryTypeInput);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="Analysis-animation <% if (enabled) { %>is-enabled<% } %> <% if (type) { %>is-<%- type %><% } %> js-animation u-flex u-alignCenter u-justifyCenter"></div>
|
||||
<div class="Analysis-info u-flex">
|
||||
<div class="ModalBlockList-itemInput CDB-Size-large">
|
||||
<input class="CDB-Radio" type="radio" value="true"
|
||||
<% if (selected) { %>checked="checked"<% } %>
|
||||
<% if (!enabled) { %>disabled="disabled"<% } %>
|
||||
>
|
||||
<span class="u-iBlock CDB-Radio-face"></span>
|
||||
</div>
|
||||
<div class="ModalBlockList-inner">
|
||||
<div class="ModalBlockList-item-header u-bSpace">
|
||||
<h2 class="ModalBlockList-item-headerTitle CDB-Text CDB-Size-large u-rSpace--m u-ellipsis" title="<%- title %>">
|
||||
<%- title %>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="ModalBlockList-item-body">
|
||||
<p class="CDB-Text CDB-Size-small u-secondaryTextColor">
|
||||
<%- desc %>
|
||||
</p>
|
||||
<% if (link) { %>
|
||||
<a class="Analysis-link CDB-Text CDB-Size-small u-tSpace-xl u-actionTextColor u-upperCase js-more track-<%- type %>-modal-learn" href="<%- link %>" target="_blank">
|
||||
<%- _t('components.modals.add-analysis.more-info') %>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var AnalysisOptionModel = require('./analysis-option-models/analysis-option-model');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
|
||||
model: function (d, opts) {
|
||||
var Model = d.Model || AnalysisOptionModel;
|
||||
|
||||
var attrs = _.omit(d, 'Model', 'nodeAttrs');
|
||||
|
||||
return new Model(attrs, {nodeAttrs: d.nodeAttrs});
|
||||
},
|
||||
|
||||
initialize: function (models, opts) {
|
||||
this.on('change:selected', this._onSelectedChange, this);
|
||||
},
|
||||
|
||||
_onSelectedChange: function (changedModel, isSelected) {
|
||||
if (isSelected) {
|
||||
this.each(function (m) {
|
||||
if (m !== changedModel) {
|
||||
m.set('selected', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
var _ = require('underscore');
|
||||
var GeneratedAnalysisOptionModel = require('./analysis-option-models/generated-analysis-option-model');
|
||||
var camshaftReference = require('camshaft-reference');
|
||||
var latestCamshaftReference = camshaftReference.getVersion('latest');
|
||||
var Analyses = require('builder/data/analyses');
|
||||
|
||||
/**
|
||||
* Analysis definitions, organized in buckets per top-level category
|
||||
*
|
||||
* - {Obj} -> options (userModel, configModel, queryGeometryModel)
|
||||
*/
|
||||
module.exports = function (options) {
|
||||
var categories = {
|
||||
create_clean: {
|
||||
title: _t('analysis-category.create-clean'),
|
||||
analyses: Analyses.getAnalysesByModalCategory('create_clean', options)
|
||||
},
|
||||
data_transformation: {
|
||||
title: _t('analysis-category.data-transformation'),
|
||||
analyses: Analyses.getAnalysesByModalCategory('data_transformation', options)
|
||||
},
|
||||
analyze_predict: {
|
||||
title: _t('analysis-category.analyze-predict'),
|
||||
analyses: Analyses.getAnalysesByModalCategory('analyze_predict', options)
|
||||
}
|
||||
};
|
||||
|
||||
var alsoGenerateFromCamshaftReference = options.userModel.featureEnabled('generate_analysis_options');
|
||||
|
||||
if (alsoGenerateFromCamshaftReference) {
|
||||
var implementedAnalyses = _.reduce(Object.keys(categories), function (memo, category) {
|
||||
return memo.concat(
|
||||
categories[category].analyses.map(function (analysis) {
|
||||
return analysis.nodeAttrs.type;
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
categories['generated'] = {
|
||||
title: 'Generated analyses by the Camshaft reference v' + _.last(camshaftReference.versions),
|
||||
analyses: _.compact(
|
||||
Object
|
||||
.keys(latestCamshaftReference.analyses)
|
||||
.filter(function (type) {
|
||||
return type !== 'source' && !_.contains(implementedAnalyses, type);
|
||||
})
|
||||
.map(function (type) {
|
||||
if (!Analyses.isAnalysisValidByType(type, options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
Model: GeneratedAnalysisOptionModel,
|
||||
nodeAttrs: {type: type},
|
||||
title: type,
|
||||
desc: JSON.stringify(latestCamshaftReference.analyses[type])
|
||||
};
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return categories;
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var AnalysisCategoryView = require('./analysis-category-pane-view');
|
||||
var createTemplateTabPane = require('builder/components/tab-pane/create-template-tab-pane');
|
||||
var template = require('./add-analyses.tpl');
|
||||
var tabPaneButtonTemplate = require('./tab-pane-button-template.tpl');
|
||||
var tabPaneTemplate = require('./tab-pane-template.tpl');
|
||||
var analysesTypes = require('./analyses-types');
|
||||
var Router = require('builder/routes/router');
|
||||
|
||||
/**
|
||||
* View to select the analysis to create.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Dialog-content Dialog-content--expanded',
|
||||
|
||||
events: {
|
||||
'click .js-add': '_onAddAnalysis'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.modalModel) throw new Error('modalModel is required');
|
||||
if (!opts.analysisOptionsCollection) throw new Error('analysisOptionsCollection is required');
|
||||
if (!opts.analysisOptions) throw new Error('analysisOptions is required');
|
||||
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
|
||||
if (!opts.queryGeometryModel) throw new Error('queryGeometryModel is required');
|
||||
|
||||
this._modalModel = opts.modalModel;
|
||||
this._analysisOptions = opts.analysisOptions;
|
||||
this._analysisOptionsCollection = opts.analysisOptionsCollection;
|
||||
this._layerDefinitionModel = opts.layerDefinitionModel;
|
||||
this._queryGeometryModel = opts.queryGeometryModel;
|
||||
|
||||
this.listenTo(this._analysisOptionsCollection, 'change:selected', this._toggleAddButton);
|
||||
this._generateTabPaneItems();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template());
|
||||
|
||||
var options = {
|
||||
tabPaneOptions: {
|
||||
template: tabPaneTemplate,
|
||||
tabPaneItemOptions: {
|
||||
tagName: 'li',
|
||||
klassName: 'CDB-NavMenu-item'
|
||||
}
|
||||
},
|
||||
tabPaneTemplateOptions: {
|
||||
tagName: 'button',
|
||||
className: 'CDB-NavMenu-link u-upperCase',
|
||||
template: tabPaneButtonTemplate
|
||||
}
|
||||
};
|
||||
|
||||
this._tabPane = createTemplateTabPane(this._tabPaneItems, options);
|
||||
this.addView(this._tabPane);
|
||||
this._$body().append(this._tabPane.render().el);
|
||||
return this;
|
||||
},
|
||||
|
||||
goToTabItem: function (tabItemName) {
|
||||
var tabItem = _.first(this._tabPane.collection.where({ name: tabItemName }));
|
||||
if (tabItem) {
|
||||
tabItem.set('selected', true);
|
||||
this._toggleAddButton(); // set the right state for the add button
|
||||
}
|
||||
},
|
||||
|
||||
_generateTabPaneItems: function () {
|
||||
var availableTypes = _.unique(_.keys(this._analysisOptions));
|
||||
|
||||
this._tabPaneItems = _.map(analysesTypes(this._analysisOptions), function (d) {
|
||||
if (_.contains(availableTypes, d.type)) {
|
||||
return d.createTabPaneItem(this._analysisOptionsCollection, {
|
||||
modalModel: this._modalModel,
|
||||
analysisOptionsCollection: this._analysisOptionsCollection,
|
||||
queryGeometryModel: this._queryGeometryModel
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this._tabPaneItems.unshift({
|
||||
label: _t('analysis-category.all'),
|
||||
name: 'all',
|
||||
createContentView: function () {
|
||||
return new AnalysisCategoryView({
|
||||
analysisType: 'all',
|
||||
modalModel: this._modalModel,
|
||||
analysisOptions: this._analysisOptions,
|
||||
analysisOptionsCollection: this._analysisOptionsCollection,
|
||||
queryGeometryModel: this._queryGeometryModel
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
_$body: function () {
|
||||
return this.$('.js-body');
|
||||
},
|
||||
|
||||
_onAddAnalysis: function () {
|
||||
var selectedOptionModel = this._analysisOptionsCollection.find(this._isSelected);
|
||||
var layerDefinitionModel = this._layerDefinitionModel;
|
||||
|
||||
if (selectedOptionModel) {
|
||||
var analysisFormAttrs = selectedOptionModel.getFormAttrs(layerDefinitionModel);
|
||||
this._modalModel.destroy(analysisFormAttrs);
|
||||
Router.goToAnalysisNode(layerDefinitionModel.get('id'), analysisFormAttrs.id);
|
||||
}
|
||||
},
|
||||
|
||||
_toggleAddButton: function () {
|
||||
this.$('.js-add').toggleClass('is-disabled', !this._analysisOptionsCollection.any(this._isSelected));
|
||||
},
|
||||
|
||||
_isSelected: function (m) {
|
||||
return !!m.get('selected');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
<svg width="308px" height="98px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g fill="#9DE0AD" fill-rule="evenodd" transform="translate(50, 8)">
|
||||
<circle class="aoi-area area01" fill="#5A7E6D" cx="20" cy="36" r="20"></circle>
|
||||
<circle class="aoi-point area-point01" cx="20" cy="36" r="4"></circle>
|
||||
<circle class="aoi-area area02" fill="#5A7E6D" cx="56" cy="28" r="20"></circle>
|
||||
<circle class="aoi-point area-point02" cx="56" cy="28" r="4"></circle>
|
||||
<circle class="aoi-area area03" fill="#5A7E6D" cx="76" cy="44" r="20"></circle>
|
||||
<circle class="aoi-point area-point03" cx="76" cy="44" r="4"></circle>
|
||||
<circle class="aoi-area area04" fill="#5A7E6D" cx="138" cy="60" r="20"></circle>
|
||||
<circle class="aoi-point area-point04" cx="138" cy="60" r="4"></circle>
|
||||
<circle class="aoi-area area05" fill="#5A7E6D" cx="156" cy="20" r="20"></circle>
|
||||
<circle class="aoi-point area-point05" cx="156" cy="20" r="4"></circle>
|
||||
<circle class="aoi-area area06" fill="#5A7E6D" cx="188" cy="28" r="20"></circle>
|
||||
<circle class="aoi-point area-point06" cx="188" cy="28" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg width="148px" height="141px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g fill="#636D72" fill-rule="evenodd">
|
||||
<circle class="Centroid-pointsHighlight" fill="#9DE0AD" cx="74" cy="70" r="4"></circle>
|
||||
<circle class="Centroid-line" fill="none" stroke="#9DE0AD" cx="74.5" cy="70" r="70"></circle>
|
||||
<circle class="Centroid-points Centroid-points01" cx="4" cy="65" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points02" cx="34" cy="85" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points03" cx="103" cy="50" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points04" cx="83" cy="40" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points05" cx="63" cy="90" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points06" cx="144" cy="65" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points07" cx="93" cy="96" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points08" cx="44" cy="55" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points09" cx="112" cy="83" r="4"></circle>
|
||||
<circle class="Centroid-points Centroid-points10" cx="24" cy="45" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,37 @@
|
||||
<svg width="306" height="98" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g transform="translate(30, 16)" fill="#636D72">
|
||||
|
||||
<g>
|
||||
<path class="Line Line-01" d="M109,32 L42,29"></path>
|
||||
<path class="Line Line-02" d="M109,32 L72,49"></path>
|
||||
<path class="Line Line-03" d="M109,32 L101,54"></path>
|
||||
<path class="Line Line-04" d="M109,32 L131,60"></path>
|
||||
<path class="Line Line-05" d="M109,32 L82,19"></path>
|
||||
<path class="Line Line-06" d="M109,32 L150,47"></path>
|
||||
<path class="Line Line-07" d="M109,32 L121,4"></path>
|
||||
<path class="Line Line-08" d="M109,32 L141,14"></path>
|
||||
<path class="Line Line-09" d="M109,32 L159,25"></path>
|
||||
</g>
|
||||
|
||||
<circle class="Dot Dot--withColorChange Dot-01" cx="42" cy="29" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-02" cx="72" cy="49" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-03" cx="101" cy="54" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-04" cx="131" cy="60" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-05" cx="82" cy="19" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-06" cx="150" cy="47" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-07" cx="141" cy="14" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-08" cx="121" cy="4" r="4"></circle>
|
||||
<circle class="Dot Dot--withColorChange Dot-09" cx="159" cy="25" r="4"></circle>
|
||||
|
||||
<circle class="Dot Dot--withFadeOut Dot-10" cx="52" cy="54" r="4"></circle>
|
||||
<circle class="Dot Dot--withFadeOut Dot-11" cx="4" cy="54" r="4"></circle>
|
||||
<circle class="Dot Dot--withFadeOut Dot-12" cx="28" cy="14" r="4"></circle>
|
||||
<circle class="Dot Dot--withFadeOut Dot-13" cx="188" cy="6" r="4"></circle>
|
||||
<circle class="Dot Dot--withFadeOut Dot-14" cx="196" cy="46" r="4"></circle>
|
||||
<circle class="Dot Dot--withFadeOut Dot-15" cx="228" cy="26" r="4"></circle>
|
||||
<circle class="Dot Dot--withFadeOut Dot-16" cx="182" cy="29" r="4"></circle>
|
||||
<circle class="Dot Dot--withFadeOut Dot-17" cx="62" cy="9" r="4"></circle>
|
||||
|
||||
<circle class="Dot" fill="white" cx="109" cy="32" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg width="148px" height="64px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g stroke="none" fill="#636D72">
|
||||
<polyline class="Line" stroke="#9DE0AD" points="4.08007812 29.7382812 34.2617188 49.4335938 24.2285156 9.83007812 43.7246094 18.7578125 62.9414062 54.7128906 73.6425781 30.9160156 92.3808594 59.359375 83.328125 3.75585938 103.042969 14.6777344 111.734375 47.1640625 144.171875 28.3574219"></polyline>
|
||||
<circle class="Dot Dot-01" cx="4" cy="29" r="4"></circle>
|
||||
<circle class="Dot Dot-02" cx="34" cy="49" r="4"></circle>
|
||||
<circle class="Dot Dot-03" cx="24" cy="9" r="4"></circle>
|
||||
<circle class="Dot Dot-04" cx="44" cy="19" r="4"></circle>
|
||||
<circle class="Dot Dot-05" cx="63" cy="54" r="4"></circle>
|
||||
<circle class="Dot Dot-06" cx="74" cy="32" r="4"></circle>
|
||||
<circle class="Dot Dot-07" cx="93" cy="60" r="4"></circle>
|
||||
<circle class="Dot Dot-08" cx="83" cy="4" r="4"></circle>
|
||||
<circle class="Dot Dot-09" cx="103" cy="14" r="4"></circle>
|
||||
<circle class="Dot Dot-10" cx="112" cy="47" r="4"></circle>
|
||||
<circle class="Dot Dot-11" cx="144" cy="29" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,54 @@
|
||||
<svg width="302px" height="98px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<rect id="path-1" x="0" y="0" width="304" height="96"></rect>
|
||||
<rect id="path-3" x="48" y="0" width="48" height="64"></rect>
|
||||
<mask id="mask-4" x="0" y="0" width="48" height="64" fill="white">
|
||||
<use xlink:href="#path-3"></use>
|
||||
</mask>
|
||||
<rect id="path-5" x="0" y="31" width="49" height="33"></rect>
|
||||
<mask id="mask-6" x="0" y="0" width="49" height="33" fill="white">
|
||||
<use xlink:href="#path-5"></use>
|
||||
</mask>
|
||||
<rect id="path-7" x="95" y="0" width="57" height="24"></rect>
|
||||
<mask id="mask-8" x="0" y="0" width="57" height="24" fill="white">
|
||||
<use xlink:href="#path-7"></use>
|
||||
</mask>
|
||||
<rect id="path-9" x="151" y="8" width="18" height="16"></rect>
|
||||
<mask id="mask-10" x="0" y="0" width="18" height="16" fill="white">
|
||||
<use xlink:href="#path-9"></use>
|
||||
</mask>
|
||||
<rect id="path-11" x="24" y="16" width="25" height="16"></rect>
|
||||
<mask id="mask-12" x="0" y="0" width="25" height="16" fill="white">
|
||||
<use xlink:href="#path-11"></use>
|
||||
</mask>
|
||||
<rect id="path-13" x="95" y="23" width="25" height="25"></rect>
|
||||
<mask id="mask-14" x="0" y="0" width="25" height="25" fill="white">
|
||||
<use xlink:href="#path-13"></use>
|
||||
</mask>
|
||||
<rect id="path-15" x="119" y="23" width="58" height="33"></rect>
|
||||
<mask id="mask-16" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="58" height="33" fill="white">
|
||||
<use xlink:href="#path-15"></use>
|
||||
</mask>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="Rectangle-10" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
<g transform="translate(64, 16)" stroke="#282C2F" stroke-width="2" fill="#9DE0AD">
|
||||
<use class="rect-01" mask="url(#mask-4)" fill-opacity="0.32" xlink:href="#path-3"></use>
|
||||
<use class="rect-02" mask="url(#mask-6)" fill-opacity="0.8" xlink:href="#path-5"></use>
|
||||
<use class="rect-03" mask="url(#mask-8)" fill-opacity="0.639999986" xlink:href="#path-7"></use>
|
||||
<use class="rect-04" mask="url(#mask-10)" fill-opacity="0.160000011" xlink:href="#path-9"></use>
|
||||
<use class="rect-05" mask="url(#mask-12)" fill-opacity="0.48" xlink:href="#path-11"></use>
|
||||
<use class="rect-06" mask="url(#mask-14)" xlink:href="#path-13"></use>
|
||||
<use class="rect-07" mask="url(#mask-16)" fill-opacity="0.800000012" xlink:href="#path-15"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,24 @@
|
||||
<svg width="306" height="98" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<circle class="Dot" cx="117" cy="33" r="4"></circle>
|
||||
<circle class="Dot" cx="91" cy="57" r="4"></circle>
|
||||
<circle class="Dot--withSplash Dot--withSplash--01" cx="91" cy="57" r="4"></circle>
|
||||
<circle class="Dot" cx="73" cy="27" r="4"></circle>
|
||||
<circle class="Dot" cx="223" cy="39" r="4"></circle>
|
||||
<circle class="Dot--withSplash Dot--withSplash--02" cx="223" cy="39" r="4"></circle>
|
||||
<circle class="Dot" cx="125" cy="69" r="4"></circle>
|
||||
<circle class="Dot" cx="64" cy="65" r="4"></circle>
|
||||
<circle class="Dot" cx="151" cy="71" r="4"></circle>
|
||||
<circle class="Dot--withSplash Dot--withSplash--03" cx="151" cy="71" r="4"></circle>
|
||||
<circle class="Dot" cx="205" cy="75" r="4"></circle>
|
||||
<circle class="Dot" cx="163" cy="61" r="4"></circle>
|
||||
<circle class="Dot" cx="133" cy="43" r="4"></circle>
|
||||
<circle class="Dot" cx="189" cy="46" r="4"></circle>
|
||||
<circle class="Dot--withSplash Dot--withSplash--04" cx="189" cy="46" r="4"></circle>
|
||||
<circle class="Dot" cx="242" cy="45" r="4"></circle>
|
||||
<circle class="Dot" cx="155" cy="39" r="4"></circle>
|
||||
<circle class="Dot" cx="195" cy="23" r="4"></circle>
|
||||
<circle class="Dot--green Dot-01" cx="91" cy="57" r="4"></circle>
|
||||
<circle class="Dot--green Dot-02" cx="223" cy="39" r="4"></circle>
|
||||
<circle class="Dot--green Dot-03" cx="151" cy="71" r="4"></circle>
|
||||
<circle class="Dot--green Dot-04" cx="189" cy="46" r="4"></circle>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,38 @@
|
||||
<svg width="304" height="96" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g fill="#636D72" stroke="none" stroke-width="1">
|
||||
<g stroke="#FFFFFF" stroke-linecap="square">
|
||||
<path class="Line" d="M174.5,58.5 L166.5,35.5" id="Line"></path>
|
||||
<path class="Line" d="M174.5,58.5 L145.5,40.5" id="Line"></path>
|
||||
<path class="Line" d="M128.5,29.5 L145.5,40.5" id="Line"></path>
|
||||
<path class="Line" d="M128.5,29.5 L84.5,24.5" id="Line"></path>
|
||||
<path class="Line--withHighlight" d="M174.5,58.5 L136.5,66.5" id="Line"></path>
|
||||
<path class="Line--withHighlight" d="M103.5,53.5 L136.5,66.5" id="Line"></path>
|
||||
<path class="Line--withHighlight" d="M76.5,61.5 L104.5,53.5" id="Line"></path>
|
||||
<path class="Line" d="M174.5,58.5 L162.5,68.5" id="Line"></path>
|
||||
<path class="Line" d="M174.5,58.5 L217.5,71.5" id="Line"></path>
|
||||
<path class="Line" d="M235.5,35.5 L217.5,71.5" id="Line"></path>
|
||||
<path class="Line" d="M235.5,35.5 L253.5,41.5" id="Line"></path>
|
||||
<path class="Line" d="M202.5,42.5 L166.5,35.5" id="Line"></path>
|
||||
<path class="Line" d="M202.5,42.5 L207.5,18.5" id="Line"></path>
|
||||
<path class="Line--withFade" d="M174.5,58.5 L136.5,66.5" id="Line"></path>
|
||||
<path class="Line--withFade" d="M103.5,53.5 L136.5,66.5" id="Line"></path>
|
||||
<path class="Line--withFade" d="M76.5,61.5 L104.5,53.5" id="Line"></path>
|
||||
</g>
|
||||
|
||||
<circle class="Dot Dot-01" cx="129" cy="30" r="4"></circle>
|
||||
<circle class="Dot Dot-02" cx="85" cy="24" r="4"></circle>
|
||||
<circle class="Dot Dot-03" cx="235" cy="36" r="4"></circle>
|
||||
<circle class="Dot Dot-04" cx="163" cy="68" r="4"></circle>
|
||||
<circle class="Dot Dot-05" cx="217" cy="72" r="4"></circle>
|
||||
<circle class="Dot Dot-06" cx="145" cy="40" r="4"></circle>
|
||||
<circle class="Dot Dot-07" cx="201" cy="43" r="4"></circle>
|
||||
<circle class="Dot Dot-08" cx="254" cy="42" r="4"></circle>
|
||||
<circle class="Dot Dot-09" cx="167" cy="36" r="4"></circle>
|
||||
<circle class="Dot Dot-10" cx="207" cy="20" r="4"></circle>
|
||||
|
||||
<circle class="Dot-11 Dot--withHighlight" cx="103" cy="54" r="4"></circle>
|
||||
<circle class="Dot-12 Dot--withHighlight" cx="137" cy="66" r="4"></circle>
|
||||
<circle class="Dot-13 Dot--withHighlight" cx="76" cy="62" r="4"></circle>
|
||||
<circle class="Dot-14 Dot--withHighlight" cx="175" cy="58" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg width="306px" height="98px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g fill="#636D72" fill-rule="evenodd" transform="translate(70, 15)">
|
||||
<polygon fill="none" class="Filter-line" stroke="#9DE0AD" points="73.703125 13.6435547 97.5439453 17.5722656 106.554688 42.9824219 92.5517578 55.5849609 63.3261719 53.5224609 54.0126953 15.3515625 59.0839844 11.4414062"></polygon>
|
||||
<circle class="Filter-dotHighlight Filter-dotHighlight--01" cx="95" cy="20" r="4"></circle>
|
||||
<circle class="Filter-dotHighlight Filter-dotHighlight--06" cx="73" cy="24" r="4"></circle>
|
||||
<circle class="Filter-dotHighlight Filter-dotHighlight--02" cx="103" cy="42" r="4"></circle>
|
||||
<circle class="Filter-dotHighlight Filter-dotHighlight--03" cx="91" cy="52" r="4"></circle>
|
||||
<circle class="Filter-dotHighlight Filter-dotHighlight--05" cx="57" cy="14" r="4"></circle>
|
||||
<circle class="Filter-dotHighlight Filter-dotHighlight--04" cx="65" cy="50" r="4"></circle>
|
||||
<circle class="Filter-dot circle08" cx="163" cy="20" r="4"></circle>
|
||||
<circle class="Filter-dot circle07" cx="31" cy="38" r="4"></circle>
|
||||
<circle class="Filter-dot circle06" cx="13" cy="8" r="4"></circle>
|
||||
<circle class="Filter-dot circle05" cx="4" cy="46" r="4"></circle>
|
||||
<circle class="Filter-dot circle04" cx="145" cy="56" r="4"></circle>
|
||||
<circle class="Filter-dot circle03" cx="129" cy="27" r="4"></circle>
|
||||
<circle class="Filter-dot circle02" cx="182" cy="26" r="4"></circle>
|
||||
<circle class="Filter-dot circle01" cx="135" cy="4" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg width="302px" height="99px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g fill="#9DE0AD" fill-rule="evenodd">
|
||||
<path d="M0.5,41.5 L305.5,24.5" class="Georeference-street00" stroke="#282C2F" stroke-width="8" stroke-linecap="square"></path>
|
||||
<path d="M134,0 L141,91" class="Georeference-street01" stroke="#282C2F" stroke-width="8" stroke-linecap="square"></path>
|
||||
<path d="M258,0 L265,91" class="Georeference-street02" stroke="#282C2F" stroke-width="8" stroke-linecap="square"></path>
|
||||
<text class="Georeference-point02-text" font-family="Open Sans" font-size="10" font-weight="300" fill="#FFFFFF">
|
||||
<tspan x="63" y="62">-63.578373</tspan>
|
||||
<tspan x="63" y="76">44.642619</tspan>
|
||||
</text>
|
||||
<text class="Georeference-point01-text" font-family="Open Sans" font-size="10" font-weight="300" fill="#FFFFFF">
|
||||
<tspan x="198" y="54">IP 214.80.33.47</tspan>
|
||||
</text>
|
||||
<circle class="Dot Dot-01" fill="#9DE0AD" cx="43" cy="40" r="8"></circle>
|
||||
<circle class="Dot Dot-02" fill="#9DE0AD" cx="183" cy="32" r="8"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,20 @@
|
||||
<svg width="176" height="64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g fill="#9DE0AD">
|
||||
<polygon fill="none" class="Group-line" stroke="#9EE0AD" points="88.4493047 0.0859375 175.58896 0.0859375 175.58896 23.2574383 135.581141 32.7431641 135.581141 54.9504597 117.092373 54.9504597 101.982508 63.6816406 59.4503976 63.6816406 44.9366094 55.6914063 1 55.6914063 1 24.8914834 44.6201536 24.8914834 56.9346563 0"></polygon>
|
||||
<circle class="Group-point Group-point01" cx="60" cy="60" r="4"></circle>
|
||||
<circle class="Group-point Group-point02" cx="100" cy="4" r="4"></circle>
|
||||
<circle class="Group-point Group-point03" cx="132" cy="52" r="4"></circle>
|
||||
<circle class="Group-point Group-point04" cx="116" cy="52" r="4"></circle>
|
||||
<circle class="Group-point Group-point05" cx="116" cy="36" r="4"></circle>
|
||||
<circle class="Group-point Group-point06" cx="116" cy="20" r="4"></circle>
|
||||
<circle class="Group-point Group-point07" cx="172" cy="20" r="4"></circle>
|
||||
<circle class="Group-point Group-point08" cx="172" cy="4" r="4"></circle>
|
||||
<circle class="Group-point Group-point09" cx="44" cy="28" r="4"></circle>
|
||||
<circle class="Group-point Group-point10" cx="4" cy="28" r="4"></circle>
|
||||
<circle class="Group-point Group-point11" cx="4" cy="52" r="4"></circle>
|
||||
<circle class="Group-point Group-point12" cx="44" cy="52" r="4"></circle>
|
||||
<circle class="Group-point Group-point13" cx="100" cy="60" r="4"></circle>
|
||||
<circle class="Group-point Group-point14" cx="132" cy="36" r="4"></circle>
|
||||
<circle class="Group-point Group-point15" cx="60" cy="4" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg width="308px" height="98px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g fill="#636D72" fill-rule="evenodd" transform="translate(55, 15)">
|
||||
<circle class="intersect intersect-layer" fill="#5A7E6D" stroke="#9DE0AD" cx="101" cy="32" r="32"></circle>
|
||||
<circle class="intersect intersect-circle1" cx="57" cy="14" r="4"></circle>
|
||||
<circle class="intersect intersect-circle2" cx="31" cy="38" r="4"></circle>
|
||||
<circle class="intersect intersect-circle3" cx="13" cy="8" r="4"></circle>
|
||||
<circle class="intersect intersect-circle4" cx="163" cy="20" r="4"></circle>
|
||||
<circle class="intersect intersect-circle5" cx="65" cy="50" r="4"></circle>
|
||||
<circle class="intersect intersect-circle6" cx="4" cy="46" r="4"></circle>
|
||||
<circle class="intersect intersect-circle1Highlight" cx="91" cy="52" r="4"></circle>
|
||||
<circle class="intersect intersect-circle7" cx="145" cy="56" r="4"></circle>
|
||||
<circle class="intersect intersect-circle2Highlight" cx="103" cy="42" r="4"></circle>
|
||||
<circle class="intersect intersect-circle3Highlight" cx="73" cy="24" r="4"></circle>
|
||||
<circle class="intersect intersect-circle4Highlight" cx="129" cy="27" r="4"></circle>
|
||||
<circle class="intersect intersect-circle8" cx="182" cy="26" r="4"></circle>
|
||||
<circle class="intersect intersect-circle5Highlight" cx="95" cy="20" r="4"></circle>
|
||||
<circle class="intersect intersect-circle9" cx="135" cy="4" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg width="185px" height="64px" viewBox="512 388 185 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Group" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(513.000000, 389.000000)">
|
||||
<polygon class="Path Path-01" stroke="#FFFFFF" points="7.92876627e-08 47 22.7004054 47 22.7004054 62.1132254 0 62.1132254"></polygon>
|
||||
<polygon class="Path Path-03" stroke="#FFFFFF" points="160 24 182.700012 24 182.700012 38.9848139 160 38.9848139"></polygon>
|
||||
<polygon class="Path Path-02" stroke="#FFFFFF" points="87.7216797 0.278320312 110.836973 0.278320312 110.836973 24.2279707 87.8211856 24.2279707"></polygon>
|
||||
<polygon class="Path Path-03Green" stroke="#9DE0AD" points="128 24 182.862039 24 182.862039 55.6141883 128.202651 55.6141883"></polygon>
|
||||
<polygon class="Path Path-01Green" stroke="#9DE0AD" points="0.0502868199 31 47.7467919 31 47.7467919 62.177811 0 62.177811"></polygon>
|
||||
<polygon class="Path Path-02Green" stroke="#9DE0AD" points="63.9970703 0.033203125 110.710209 0.033203125 110.710209 62.0109574 63.8597689 62.0109574"></polygon>
|
||||
<circle class="Dot Dot-01" fill="#FFFFFF" cx="7" cy="55" r="4"></circle>
|
||||
<circle class="Dot Dot-02" fill="#FFFFFF" cx="99" cy="11" r="4"></circle>
|
||||
<circle class="Dot Dot-03" fill="#FFFFFF" cx="171" cy="31" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<svg width="306" height="98" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g transform="translate(50, 20)">
|
||||
<circle class="Dot Dot--01 Dot--gray" cx="4" cy="46" r="4"></circle>
|
||||
<circle class="Dot Dot--02 Dot--gray" cx="31" cy="38" r="4"></circle>
|
||||
<circle class="Dot Dot--03 Dot--gray" cx="13" cy="8" r="4"></circle>
|
||||
<circle class="Dot Dot--04 Dot--green" cx="57" cy="14" r="4"></circle>
|
||||
<circle class="Dot Dot--05 Dot--green" cx="65" cy="50" r="4"></circle>
|
||||
<circle class="Dot Dot--06 Dot--green" cx="91" cy="52" r="4"></circle>
|
||||
<circle class="Dot Dot--07 Dot--green" cx="103" cy="42" r="4"></circle>
|
||||
<circle class="Dot Dot--08 Dot--green" cx="73" cy="24" r="4"></circle>
|
||||
<circle class="Dot Dot--09 Dot--green" cx="95" cy="20" r="4"></circle>
|
||||
<circle class="Dot Dot--10 Dot--darkGray" cx="145" cy="56" r="4"></circle>
|
||||
<circle class="Dot Dot--11 Dot--darkGray" cx="129" cy="27" r="4"></circle>
|
||||
<circle class="Dot Dot--12 Dot--darkGray" cx="135" cy="4" r="4"></circle>
|
||||
<circle class="Dot Dot--13 Dot--white" cx="163" cy="20" r="4"></circle>
|
||||
<circle class="Dot Dot--14 Dot--white" cx="182" cy="26" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,36 @@
|
||||
<svg width="306px" height="98px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Mod-Analysis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g fill="#9DE0AD">
|
||||
<text x="8" y="19" class="Text Text-LH" font-family="OpenSans, Open Sans" font-size="10" font-weight="normal">
|
||||
<%- _t('components.modals.add-analysis.option-types.moran-cluster.low-high') %>
|
||||
</text>
|
||||
<text x="227" y="19" class="Text Text-HH" font-family="OpenSans, Open Sans" font-size="10" font-weight="normal">
|
||||
<%- _t('components.modals.add-analysis.option-types.moran-cluster.high-high') %>
|
||||
</text>
|
||||
<text x="233" y="85" class="Text Text-HL" font-family="OpenSans, Open Sans" font-size="10" font-weight="normal">
|
||||
<%- _t('components.modals.add-analysis.option-types.moran-cluster.high-low') %>
|
||||
</text>
|
||||
<text x="10" y="85" class="Text Text-LL" font-family="OpenSans, Open Sans" font-size="10" font-weight="normal">
|
||||
<%- _t('components.modals.add-analysis.option-types.moran-cluster.low-low') %>
|
||||
</text>
|
||||
</g>
|
||||
<g fill="#FFF">
|
||||
<circle class="Dot Dot-04" fill-opacity="0.4" cx="106" cy="57" r="4"></circle>
|
||||
<circle class="Dot Dot-04" fill-opacity="0.4" cx="137" cy="66" r="4"></circle>
|
||||
<circle class="Dot Dot-04" fill-opacity="0.4" cx="79" cy="62" r="4"></circle>
|
||||
<circle class="Dot Dot-06" fill-opacity="0.6" cx="193" cy="70" r="4"></circle>
|
||||
<circle class="Dot Dot-06" fill-opacity="0.6" cx="175" cy="58" r="4"></circle>
|
||||
<circle class="Dot Dot-08" fill-opacity="0.8" cx="129" cy="30" r="4"></circle>
|
||||
<circle class="Dot Dot-08" fill-opacity="0.8" cx="145" cy="40" r="4"></circle>
|
||||
<circle class="Dot Dot-1" cx="231" cy="36" r="4"></circle>
|
||||
<circle class="Dot Dot-1" cx="197" cy="40" r="4"></circle>
|
||||
<circle class="Dot Dot-1" cx="250" cy="38" r="4"></circle>
|
||||
<circle class="Dot Dot-1" cx="167" cy="36" r="4"></circle>
|
||||
<circle class="Dot Dot-1" cx="203" cy="20" r="4"></circle>
|
||||
</g>
|
||||
<g stroke="#9DE0AD">
|
||||
<path d="M0,49 L304,49" class="Line Line-horizontal" stroke-dasharray="2,3"></path>
|
||||
<path d="M152,0 L152,98" class="Line Line-vertical" stroke-dasharray="2,3"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,37 @@
|
||||
<svg width="306px" height="98px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g transform="translate(87, 22)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
|
||||
<path class="Arrow-04" d="M98.5,45.5 L97.5,40.5" stroke="#9CE0AC"></path>
|
||||
<path class="Arrow-04" d="M98.5,45.5 L92.5,46.5" stroke="#9CE0AC"></path>
|
||||
<path class="Line-04" d="M75.5,34.5 L98.5,45.5" stroke="#9DE0AD"></path>
|
||||
|
||||
<path class="Line-03" d="M0.5,44.5 L3.5,39.5" stroke="#9CE0AC"></path>
|
||||
<path class="Line-03" d="M0.5,44.5 L5.5,47.5" stroke="#9CE0AC"></path>
|
||||
<path class="Line-03" d="M0.5,44.5 L55.5,32.5" stroke="#9DE0AD"></path>
|
||||
|
||||
<g transform="translate(75.000000, 1.000000)">
|
||||
<path class="Arrow-01" d="M31.5,8.5 L32.5,2.5" stroke="#95D6A6"></path>
|
||||
<path class="Arrow-01" d="M26.5,0.5 L32.5,2.5" stroke="#95D6A6"></path>
|
||||
<path class="Line-01" d="M0.5,18.5 L32.5,2.5" stroke="#9DE0AD"></path>
|
||||
</g>
|
||||
|
||||
<g transform="translate(19.000000, 0.000000)" stroke="#979797">
|
||||
<path class="Arrow-02" d="M36.5,19.5 L35.5,14.5"></path>
|
||||
<path class="Arrow-02" d="M36.5,19.5 L31.5,21.5"></path>
|
||||
<path class="Line-02" d="M0.5,0.5 L36.5,19.5"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g stroke="#636D72">
|
||||
<path d="M0,49 L304,49" class="Line Line-horizontal" stroke-dasharray="2,3"></path>
|
||||
<path d="M152,0 L152,98" class="Line Line-vertical" stroke-dasharray="2,3"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(72, 11)">
|
||||
<circle class="Dot Dot--center" cx="80" cy="38" r="4" stroke="#9DE0AD" fill="#2E3C43"></circle>
|
||||
<circle class="Dot Dot-01" fill="#9DE0AD" cx="133" cy="10" r="4"></circle>
|
||||
<circle class="Dot Dot-02" fill="#979EA1" cx="24" cy="4" r="4"></circle>
|
||||
<circle class="Dot Dot-03" fill="#9DE0AD" cx="4" cy="57" r="4"></circle>
|
||||
<circle class="Dot Dot-04" fill="#9DE0AD" cx="122" cy="61" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg width="306" height="98" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g transform="translate(30, 16)" fill="#9DE0AD">
|
||||
<circle class="Dot" cx="42" cy="29" r="4"></circle>
|
||||
<circle class="Dot--withFadeOut Dot-01" cx="72" cy="49" r="4"></circle>
|
||||
<circle class="Dot" cx="52" cy="54" r="4"></circle>
|
||||
<circle class="Dot--withFadeOut Dot-02" cx="4" cy="54" r="4"></circle>
|
||||
<circle class="Dot--withFadeOut Dot-03" cx="28" cy="14" r="4"></circle>
|
||||
<circle class="Dot" cx="188" cy="6" r="4"></circle>
|
||||
<circle class="Dot" cx="196" cy="46" r="4"></circle>
|
||||
<circle class="Dot--withFadeOut Dot-03" cx="159" cy="25" r="4"></circle>
|
||||
<circle class="Dot" cx="228" cy="26" r="4"></circle>
|
||||
<circle class="Dot" cx="141" cy="14" r="4"></circle>
|
||||
<circle class="Dot--withFadeOut Dot-04" cx="121" cy="4" r="4"></circle>
|
||||
<circle class="Dot" cx="101" cy="54" r="4"></circle>
|
||||
<circle class="Dot" cx="182" cy="29" r="4"></circle>
|
||||
<circle class="Dot--withFadeOut Dot-05" cx="131" cy="60" r="4"></circle>
|
||||
<circle class="Dot" cx="82" cy="19" r="4"></circle>
|
||||
<circle class="Dot" cx="150" cy="47" r="4"></circle>
|
||||
<circle class="Dot--withFadeOut Dot-06" cx="62" cy="9" r="4"></circle>
|
||||
<circle class="Dot" cx="109" cy="32" r="4"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<%- label %>
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="Modal-navigation">
|
||||
<ul class="Modal-navigationInner CDB-Text is-semibold CDB-Size-medium js-menu"></ul>
|
||||
</div>
|
||||
<div class="Modal-inner Modal-inner--with-navigation js-content">
|
||||
</div>
|
||||
@@ -0,0 +1,136 @@
|
||||
var Backbone = require('backbone');
|
||||
var XYZModel = require('./xyz/xyz-model');
|
||||
var mosaicThumbnail = require('builder/components/mosaic/mosaic-thumbnail.tpl');
|
||||
var MapboxModel = require('./mapbox/mapbox-model');
|
||||
var WMSModel = require('./wms/wms-model');
|
||||
var TileJSONModel = require('./tilejson/tilejson-model');
|
||||
var NASAModel = require('./nasa/nasa-model');
|
||||
|
||||
/**
|
||||
* Add basemap model
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
tabs: undefined,
|
||||
contentPane: 'tabs', // [tabs, loading, error]
|
||||
currentTab: 'xyz' // [xyz, wms, nasa, mapbox, tilejson, nasa]
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
this._currentTab = opts.currentTab;
|
||||
|
||||
this._initTabs();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
activeTabModel: function () {
|
||||
return this.get('tabs').findWhere({ name: this.get('currentTab') });
|
||||
},
|
||||
|
||||
canSaveBasemap: function () {
|
||||
return this.get('contentPane') === 'tabs' && this._layerToSave();
|
||||
},
|
||||
|
||||
saveBasemap: function () {
|
||||
var self = this;
|
||||
|
||||
this.set('contentPane', 'addingBasemap');
|
||||
|
||||
var customBaselayerModel = this._layerToSave();
|
||||
var attrs = customBaselayerModel.getAttributes();
|
||||
|
||||
if (this.activeTabModel().hasAlreadyAddedLayer(this._customBaselayersCollection)) {
|
||||
// update selected in basemaps collection
|
||||
this._basemapsCollection.updateSelected(attrs.className);
|
||||
// update category if needed in basemaps collection,
|
||||
// this is the case for mapbox basemaps which were stored without category in the editor
|
||||
this._basemapsCollection.updateCategory(attrs.className, attrs.category);
|
||||
|
||||
// update baselayer
|
||||
this._onBasemapSaved(attrs);
|
||||
} else {
|
||||
// Add to customBaselayersCollection before saving, so save URL resolves to the expected endpoint
|
||||
this._customBaselayersCollection.add(customBaselayerModel);
|
||||
|
||||
customBaselayerModel.save({}, {
|
||||
success: function (mdl, mdlAttrs) {
|
||||
var options = mdlAttrs.options;
|
||||
|
||||
var name = options.name ? options.name : 'Custom basemap ' + mdlAttrs.order;
|
||||
var className = options.className;
|
||||
var urlTemplate = options.urlTemplate;
|
||||
|
||||
// add in basemaps collection
|
||||
self._basemapsCollection.add({
|
||||
id: mdlAttrs.id,
|
||||
urlTemplate: urlTemplate,
|
||||
maxZoom: options.minZoom || 21,
|
||||
minZoom: options.minZoom || 0,
|
||||
name: name,
|
||||
className: className,
|
||||
attribution: options.attribution,
|
||||
category: options.category,
|
||||
tms: options.tms,
|
||||
type: options.type,
|
||||
val: className,
|
||||
label: name,
|
||||
template: function (imgURL) {
|
||||
return mosaicThumbnail({
|
||||
imgURL: imgURL
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// update baselayer
|
||||
self._onBasemapSaved(attrs);
|
||||
},
|
||||
error: function () {
|
||||
// Cleanup, remove layer it could not be saved!
|
||||
self._customBaselayersCollection.remove(customBaselayerModel);
|
||||
self.set('contentPane', 'addBasemapFailed');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onBasemapSaved: function (layerAttrs) {
|
||||
// Update baseLayer
|
||||
this._layerDefinitionsCollection.setBaseLayer(layerAttrs);
|
||||
|
||||
this.trigger('saveBasemapDone');
|
||||
},
|
||||
|
||||
_initTabs: function () {
|
||||
var tabs = new Backbone.Collection([
|
||||
new XYZModel(),
|
||||
new MapboxModel(),
|
||||
new WMSModel(null, {
|
||||
customBaselayersCollection: this._customBaselayersCollection
|
||||
}),
|
||||
new TileJSONModel(),
|
||||
new NASAModel()
|
||||
]);
|
||||
this.set({
|
||||
tabs: tabs,
|
||||
currentTab: this._currentTab || tabs.first().get('name')
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.get('tabs').each(function (tabModel) {
|
||||
tabModel.bind('saveBasemap', this.saveBasemap, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
_layerToSave: function () {
|
||||
return this.activeTabModel().get('layer');
|
||||
}
|
||||
|
||||
});
|
||||
106
lib/assets/javascripts/builder/components/modals/add-basemap/add-basemap-view.js
Executable file
@@ -0,0 +1,106 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./add-basemap.tpl');
|
||||
var TabPaneView = require('builder/components/tab-pane/tab-pane-view');
|
||||
var TabPaneCollection = require('builder/components/tab-pane/tab-pane-collection');
|
||||
var ViewFactory = require('builder/components/view-factory');
|
||||
var renderLoading = require('builder/components/loading/render-loading');
|
||||
var ErrorView = require('builder/components/error/error-view');
|
||||
var _ = require('underscore');
|
||||
var TabsView = require('./tabs-view');
|
||||
|
||||
/**
|
||||
* Add basemap dialog
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Dialog-content Dialog-content--expanded',
|
||||
|
||||
events: {
|
||||
'click .js-ok': 'ok'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.modalModel) throw new TypeError('modalModel is required');
|
||||
if (!opts.createModel) throw new TypeError('createModel is required');
|
||||
|
||||
this._modalModel = opts.modalModel;
|
||||
this._createModel = opts.createModel;
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
this._initViews();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._createModel.bind('saveBasemapDone', this._modalModel.destroy.bind(this._modalModel), this);
|
||||
this._createModel.bind('change:contentPane', this._onChangeContentView, this);
|
||||
this.add_related_model(this._createModel);
|
||||
},
|
||||
|
||||
_onChangeContentView: function () {
|
||||
var context = this._createModel.get('contentPane');
|
||||
var paneModel = _.first(this._tabPaneCollection.where({ name: context }));
|
||||
paneModel.set('selected', true);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var self = this;
|
||||
|
||||
this._submitButtom = this.$('.js-ok');
|
||||
this._modalFooter = this.$('.js-Modal-footer');
|
||||
|
||||
this._tabPaneCollection = new TabPaneCollection([
|
||||
{
|
||||
name: 'tabs',
|
||||
selected: this._createModel.get('contentPane') === 'tabs',
|
||||
createContentView: function () {
|
||||
return new TabsView({
|
||||
model: self._createModel,
|
||||
submitButton: self._submitButtom,
|
||||
modalFooter: self._modalFooter
|
||||
});
|
||||
}
|
||||
}, {
|
||||
name: 'addingBasemap',
|
||||
selected: this._createModel.get('contentPane') === 'addingBasemap',
|
||||
createContentView: function () {
|
||||
self._disableModalFooter(true);
|
||||
|
||||
return ViewFactory.createByHTML(
|
||||
renderLoading({
|
||||
title: _t('components.modals.add-basemap.adding-basemap')
|
||||
})
|
||||
);
|
||||
}
|
||||
}, {
|
||||
name: 'addBasemapFailed',
|
||||
selected: this._createModel.get('contentPane') === 'addBasemapFailed',
|
||||
createContentView: function () {
|
||||
return new ErrorView({
|
||||
title: _t('components.modals.add-basemap.add-basemap-error')
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
var tabPaneView = new TabPaneView({
|
||||
collection: this._tabPaneCollection
|
||||
});
|
||||
this.addView(tabPaneView);
|
||||
this.$('.js-content-container').append(tabPaneView.render().el);
|
||||
},
|
||||
|
||||
_disableModalFooter: function (disable) {
|
||||
this._modalFooter.toggleClass('is-disabled', disable);
|
||||
},
|
||||
|
||||
ok: function () {
|
||||
if (this._createModel.canSaveBasemap()) {
|
||||
this._createModel.saveBasemap();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
19
lib/assets/javascripts/builder/components/modals/add-basemap/add-basemap.tpl
Executable file
@@ -0,0 +1,19 @@
|
||||
<div class="Modal">
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-headerContainer">
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-mainTextColor u-bSpace"><%- _t('components.modals.add-basemap.modal-title') %></h2>
|
||||
<h3 class="CDB-Text CDB-Size-medium u-altTextColor"><%- _t('components.modals.add-basemap.modal-desc') %></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-container">
|
||||
<div class="Tab-pane js-content-container">
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-footer js-Modal-footer">
|
||||
<div class="Modal-footerContainer u-flex u-justifyEnd">
|
||||
<button class="CDB-Button CDB-Button--primary is-disabled js-ok ok">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-basemap.add-btn') %></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="u-flex u-alignCenter Modal-basemapContainer">
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor Modal-titleBasemap"><%- _t('components.modals.add-basemap.mapbox.insert') %></h3>
|
||||
<div class="CDB-Text u-flex u-alignCenter ">
|
||||
<label class="Metadata-label Metadata-label--big CDB-Text CDB-Size-small is-semibold u-upperCase u-ellipsis"><%- _t('components.modals.add-basemap.mapbox.enter') %></label>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<input type="text" class="CDB-InputText CDB-Text js-url" value="<%- url %>" placeholder="<%- _t('components.modals.add-basemap.xyz.eg') %> https://api.mapbox.com/styles/v1/username/basemap/tiles/256/{z}/{x}/{y}@2x?access_token=access_token">
|
||||
<div class="XYZPanel-error CDB-InfoTooltip CDB-InfoTooltip--left is-error CDB-Text CDB-Size-medium CDB-InfoTooltip-text js-error <%- lastErrorMsg ? 'is-visible' : '' %>"><%- lastErrorMsg %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex">
|
||||
<label class="Metadata-label Metadata-label--big CDB-Text CDB-Size-small is-semibold u-upperCase u-ellipsis"></label>
|
||||
<div class="Form-rowData Form-rowData--noMinHeight Form-rowData--longer">
|
||||
<p class="CDB-Text CDB-Size-small Form-rowInfoText--block u-altTextColor">
|
||||
Learn how to get your Mapbox Style URL <a href="https://www.mapbox.com/help/carto/" target="_blank">here</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var MapboxView = require('./mapbox-view');
|
||||
var MapboxToTileLayerFactory = require('./mapbox-to-tile-layer-factory');
|
||||
|
||||
/**
|
||||
* View model for Mapbox tab content.
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
name: 'mapbox',
|
||||
label: 'Mapbox',
|
||||
currentView: 'enterURL', //, validatingInputs, valid
|
||||
lastErrorMsg: '', // set if fails to save
|
||||
layer: undefined // will be set when valid
|
||||
},
|
||||
|
||||
createView: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
if (!opts.modalFooter) throw new Error('modalFooter is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this._modalFooter = opts.modalFooter;
|
||||
|
||||
this.set({
|
||||
currentView: 'enterURL',
|
||||
layer: undefined
|
||||
});
|
||||
|
||||
return new MapboxView({
|
||||
model: this,
|
||||
submitButton: this._submitButton,
|
||||
modalFooter: this._modalFooter
|
||||
});
|
||||
},
|
||||
|
||||
hasAlreadyAddedLayer: function (userLayers) {
|
||||
var urlTemplate = this.get('layer').get('urlTemplate');
|
||||
return _.any(userLayers.isCustomCategory(), function (customLayer) {
|
||||
return customLayer.get('urlTemplate') === urlTemplate;
|
||||
});
|
||||
},
|
||||
|
||||
validateInputs: function (url) {
|
||||
this.set({
|
||||
currentView: 'validatingInputs',
|
||||
url: url
|
||||
});
|
||||
|
||||
var self = this;
|
||||
|
||||
var mf = new MapboxToTileLayerFactory({
|
||||
url: url
|
||||
});
|
||||
mf.createTileLayer({
|
||||
success: function (tileLayer) {
|
||||
self.set('layer', tileLayer);
|
||||
self.trigger('saveBasemap');
|
||||
},
|
||||
error: function (errorMsg) {
|
||||
self.set({
|
||||
currentView: 'enterURL',
|
||||
lastErrorMsg: errorMsg
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
/* global Image, location */
|
||||
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CustomBaselayerModel = require('builder/data/custom-baselayer-model');
|
||||
|
||||
/**
|
||||
* Factory to create a CustomBaselayerModel from a given Integration URL for Mapbox.
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
url: ''
|
||||
},
|
||||
|
||||
_MAPBOX: {
|
||||
version: 4,
|
||||
https: 'https://dnv9my2eseobd.cloudfront.net',
|
||||
base: 'https://a.tiles.mapbox.com/'
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} callbacks
|
||||
* success {Function} given a new TileLayer object
|
||||
* error {Function} given an error explanation
|
||||
*/
|
||||
createTileLayer: function (callbacks) {
|
||||
var val = this.get('url');
|
||||
var url = this._lowerXYZ(val);
|
||||
var type = 'json';
|
||||
var subdomains = ['a', 'b', 'c'];
|
||||
var mapbox_id;
|
||||
|
||||
// Detects the URL's type
|
||||
if (url.indexOf('{x}') < 0 && url.indexOf('tiles.mapbox.com') !== -1) {
|
||||
mapbox_id = this._getMapBoxMapID(url);
|
||||
if (mapbox_id) {
|
||||
type = 'mapbox_id';
|
||||
url = mapbox_id;
|
||||
}
|
||||
} else if (url.indexOf('{x}') !== -1) {
|
||||
type = 'xyz';
|
||||
url = url.replace(/\{s\}/g, function () {
|
||||
return subdomains[Math.floor(Math.random() * 3)];
|
||||
})
|
||||
.replace(/\{x\}/g, '0')
|
||||
.replace(/\{y\}/g, '0')
|
||||
.replace(/\{z\}/g, '0');
|
||||
} else if (url && url.indexOf('http') < 0 && url.match(/(.*?)\.(.*)/) != null && url.match(/(.*?)\.(.*)/).length === 3) {
|
||||
type = 'mapbox_id';
|
||||
mapbox_id = val;
|
||||
} else { // If not, check https
|
||||
url = this._fixHTTPS(url);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var image;
|
||||
if (type === 'mapbox') {
|
||||
callbacks.success(this._newTileLayer({ tiles: [url] }));
|
||||
} else if (type === 'xyz') {
|
||||
image = new Image();
|
||||
image.onload = function () {
|
||||
callbacks.success(self._newTileLayer({
|
||||
tiles: [self._lowerXYZ(val)]
|
||||
}));
|
||||
};
|
||||
image.onerror = function () {
|
||||
callbacks.error(self._errorToMsg());
|
||||
};
|
||||
image.src = url;
|
||||
} else if (type === 'mapbox_id') {
|
||||
var base_url = this._MAPBOX.base + 'v' + this._MAPBOX.version + '/' + mapbox_id;
|
||||
var tile_url = base_url + '/{z}/{x}/{y}.png';
|
||||
var json_url = base_url + '.json';
|
||||
|
||||
// JQuery has a faulty implementation of the getJSON method and doesn't return
|
||||
// a 404, so we use a timeout. TODO: replace with CORS
|
||||
var errorTimeout = setTimeout(function () {
|
||||
callbacks.error(self._errorToMsg());
|
||||
}, 5000);
|
||||
|
||||
$.ajax({
|
||||
url: json_url,
|
||||
success: function (data) {
|
||||
clearTimeout(errorTimeout);
|
||||
callbacks.success(self._newTileLayer({
|
||||
tiles: [tile_url],
|
||||
attribution: data.attribution,
|
||||
minzoom: data.minzoom,
|
||||
maxzoom: data.maxzoom,
|
||||
name: data.name
|
||||
}));
|
||||
},
|
||||
error: function (e) {
|
||||
clearTimeout(errorTimeout);
|
||||
callbacks.error(self._errorToMsg(e));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callbacks.error(this._errorToMsg());
|
||||
}
|
||||
},
|
||||
|
||||
_newTileLayer: function (data) {
|
||||
// Check if the respond is an array
|
||||
// In that case, get only the first
|
||||
if (_.isArray(data) && _.size(data) > 0) {
|
||||
data = _.first(data);
|
||||
}
|
||||
|
||||
var url = data.tiles[0];
|
||||
var attribution = data.attribution || null;
|
||||
|
||||
var layer = new CustomBaselayerModel({
|
||||
urlTemplate: url,
|
||||
attribution: attribution,
|
||||
maxZoom: data.maxzoom || 21,
|
||||
minZoom: data.minzoom || 0,
|
||||
name: data.name || '',
|
||||
category: 'Mapbox',
|
||||
type: 'Tiled'
|
||||
});
|
||||
layer.set('className', layer._generateClassName(url));
|
||||
|
||||
return layer;
|
||||
},
|
||||
|
||||
_errorToMsg: function (error) {
|
||||
if (typeof error === 'object' || !error) {
|
||||
if (error && error.status && error.status === 401) {
|
||||
return _t('components.modals.add-basemap.mapbox.error');
|
||||
} else {
|
||||
return _t('components.modals.add-basemap.mapbox.invalid');
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
},
|
||||
|
||||
_lowerXYZ: function (url) {
|
||||
return url.replace(/\{S\}/g, '{s}')
|
||||
.replace(/\{X\}/g, '{x}')
|
||||
.replace(/\{Y\}/g, '{y}')
|
||||
.replace(/\{Z\}/g, '{z}');
|
||||
},
|
||||
|
||||
// Extracts the Mapbox MapId from a Mapbox URL
|
||||
_getMapBoxMapID: function (url) {
|
||||
// http://d.tiles.mapbox.com/v3/{user}.{map}/3/4/3.png
|
||||
// http://a.tiles.mapbox.com/v3/{user}.{map}/page.html
|
||||
// http://a.tiles.mapbox.com/v4/{user}.{map}.*
|
||||
var reg1 = /https?:\/\/[a-z]?\.?tiles\.mapbox.com\/v(\d)\/([^\/.]*)\.([^\/.]*)/;
|
||||
|
||||
// https://tiles.mapbox.com/{user}/edit/{map}?newmap&preset=Streets#3/0.00/-0.09
|
||||
var reg2 = /https?:\/\/tiles\.mapbox\.com\/(.*?)\/edit\/(.*?)(\?|#)/;
|
||||
|
||||
var match = '';
|
||||
|
||||
// Check first expresion
|
||||
match = url.match(reg1);
|
||||
|
||||
if (match && match[1] && match[2]) {
|
||||
return match[2] + '.' + match[3];
|
||||
}
|
||||
|
||||
// Check second expresion
|
||||
match = url.match(reg2);
|
||||
|
||||
if (match && match[1] && match[2]) {
|
||||
return match[1] + '.' + match[2];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* return a https url if the current application is loaded from https
|
||||
*/
|
||||
_fixHTTPS: function (url, loc) {
|
||||
loc = loc || location;
|
||||
|
||||
// fix the url to https or http
|
||||
if (url.indexOf('https') !== 0 && loc.protocol === 'https:') {
|
||||
// search for mapping
|
||||
var i = url.indexOf('mapbox.com');
|
||||
if (i !== -1) {
|
||||
return this._MAPBOX.https + url.substr(i + 'mapbox.com'.length);
|
||||
}
|
||||
return url.replace(/http/, 'https');
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var ViewFactory = require('builder/components/view-factory');
|
||||
var renderLoading = require('builder/components/loading/render-loading');
|
||||
var enterUrl = require('./enter-url.tpl');
|
||||
|
||||
/**
|
||||
* Represents the Mapbox tab content.
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'keydown': '_onKeyDown',
|
||||
'keyup': '_onKeyUp'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
if (!opts.modalFooter) throw new Error('modalFooter is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this._modalFooter = opts.modalFooter;
|
||||
this._onClickBinded = this._onClickOK.bind(this);
|
||||
|
||||
this._bindSubmitButton();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var view;
|
||||
|
||||
switch (this.model.get('currentView')) {
|
||||
case 'validatingInputs':
|
||||
this._disableModalFooter(true);
|
||||
|
||||
view = ViewFactory.createByHTML(
|
||||
renderLoading({
|
||||
title: _t('components.modals.add-basemap.validating')
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'enterURL':
|
||||
default:
|
||||
this._disableModalFooter(false);
|
||||
|
||||
view = ViewFactory.createByHTML(
|
||||
enterUrl({
|
||||
url: this.model.get('url'),
|
||||
lastErrorMsg: this.model.get('lastErrorMsg')
|
||||
})
|
||||
);
|
||||
}
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
|
||||
this._updateOkBtn();
|
||||
this._onKeyUp();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this.render, this);
|
||||
},
|
||||
|
||||
_hasValues: function () {
|
||||
return this._urlVal();
|
||||
},
|
||||
|
||||
_urlVal: function () {
|
||||
return this.$('.js-url').val();
|
||||
},
|
||||
|
||||
_onClickOK: function (e) {
|
||||
this.killEvent(e);
|
||||
|
||||
if (this._hasValues()) {
|
||||
var url = this._urlVal();
|
||||
|
||||
this.model.validateInputs(url);
|
||||
}
|
||||
},
|
||||
|
||||
_disableModalFooter: function (disable) {
|
||||
this._modalFooter.toggleClass('is-disabled', disable);
|
||||
},
|
||||
|
||||
_updateOkBtn: function () {
|
||||
this._submitButton.find('span').text(_t('components.modals.add-basemap.add-btn'));
|
||||
},
|
||||
|
||||
_onKeyDown: function (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
this.$('.js-error').removeClass('is-visible');
|
||||
},
|
||||
|
||||
_onKeyUp: function (e) {
|
||||
e && e.stopPropagation();
|
||||
this._submitButton.toggleClass('is-disabled', !this._hasValues());
|
||||
},
|
||||
|
||||
_bindSubmitButton: function () {
|
||||
this._submitButton.on('click', this._onClickBinded);
|
||||
},
|
||||
|
||||
_unBindSubmitButton: function () {
|
||||
this._submitButton.off('click', this._onClickBinded);
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._unBindSubmitButton();
|
||||
CoreView.prototype.clean.call(this);
|
||||
}
|
||||
|
||||
});
|
||||
107
lib/assets/javascripts/builder/components/modals/add-basemap/nasa/nasa-model.js
Executable file
@@ -0,0 +1,107 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var moment = require('moment');
|
||||
var NASAView = require('./nasa-view');
|
||||
var CustomBaselayerModel = require('builder/data/custom-baselayer-model');
|
||||
|
||||
var TYPES = {
|
||||
day: {
|
||||
url: 'http://map1.vis.earthdata.nasa.gov/wmts-webmerc/MODIS_Terra_CorrectedReflectance_TrueColor/default/<%- date %>/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpeg',
|
||||
limit: '2012-05-01',
|
||||
default: '2012-05-01',
|
||||
attribution: '<a href="http://earthdata.nasa.gov/gibs" target="_blank">NASA EOSDIS GIBS</a>',
|
||||
name: 'NASA Terra',
|
||||
maxZoom: 9,
|
||||
minZoom: 1
|
||||
},
|
||||
night: {
|
||||
url: 'http://map1.vis.earthdata.nasa.gov/wmts-webmerc/VIIRS_CityLights_2012/default/<%- date %>/GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpeg',
|
||||
limit: '2012-05-01',
|
||||
default: '2012-05-02',
|
||||
attribution: '<a href="http://earthdata.nasa.gov/gibs" target="_blank">NASA EOSDIS GIBS</a>',
|
||||
name: 'NASA Earth at night',
|
||||
maxZoom: 8,
|
||||
minZoom: 1
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* View model for NASA tab content.
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
name: 'nasa',
|
||||
label: 'NASA',
|
||||
layer: undefined, // gets set on dayOrNight/date changes
|
||||
layerType: 'day',
|
||||
date: undefined, // for date picker
|
||||
current: undefined,
|
||||
format: 'Y-m-d' // YYYY-MM-DD
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
createView: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
|
||||
var utc = new Date().getTimezoneOffset();
|
||||
var today = moment(new Date()).utcOffset(utc).format('YYYY-MM-DD');
|
||||
var yesterday = moment(new Date()).utcOffset(utc).subtract(1, 'days').format('YYYY-MM-DD');
|
||||
|
||||
this.set({
|
||||
current: today,
|
||||
date: yesterday
|
||||
});
|
||||
|
||||
return new NASAView({
|
||||
model: this,
|
||||
submitButton: this._submitButton
|
||||
});
|
||||
},
|
||||
|
||||
hasAlreadyAddedLayer: function (userLayers) {
|
||||
var urlTemplate = this.get('layer').get('urlTemplate');
|
||||
return _.any(userLayers.isCustomCategory(), function (customLayer) {
|
||||
return customLayer.get('urlTemplate') === urlTemplate;
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.bind('change:date change:layerType', this._onChange, this);
|
||||
},
|
||||
|
||||
_onChange: function () {
|
||||
var dateStr = this.get('date');
|
||||
var layerType = this.get('layerType');
|
||||
|
||||
var url = _.template(TYPES[layerType].url)({
|
||||
date: dateStr
|
||||
});
|
||||
|
||||
var name = TYPES[layerType].name;
|
||||
|
||||
if (layerType === 'day') {
|
||||
name = name + ' ' + dateStr;
|
||||
}
|
||||
|
||||
var layer = new CustomBaselayerModel({
|
||||
urlTemplate: url,
|
||||
attribution: TYPES[layerType].attribution,
|
||||
maxZoom: TYPES[layerType].maxZoom,
|
||||
minZoom: TYPES[layerType].minZoom,
|
||||
name: name,
|
||||
category: 'NASA',
|
||||
type: 'Tiled'
|
||||
});
|
||||
layer.set('className', layer._generateClassName(url));
|
||||
|
||||
this.set('layer', layer);
|
||||
}
|
||||
|
||||
});
|
||||
106
lib/assets/javascripts/builder/components/modals/add-basemap/nasa/nasa-view.js
Executable file
@@ -0,0 +1,106 @@
|
||||
var $ = require('jquery');
|
||||
var moment = require('moment');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var DatePickerView = require('builder/components/date-picker/date-picker-view');
|
||||
var EditFieldModel = require('builder/components/date-picker/edit-field-model');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var template = require('./nasa.tpl');
|
||||
|
||||
/**
|
||||
* Represents the NASA tab content
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
options: {
|
||||
dateFormat: 'YYYY-MM-DD'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .js-day': '_onChangeToDay',
|
||||
'click .js-night': '_onChangeToNight'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this.dateModel = new EditFieldModel({
|
||||
value: this.model.get('date'),
|
||||
type: 'date'
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this._updateOkBtn();
|
||||
this._disableOkBtn(false);
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
layerType: this.model.get('layerType')
|
||||
})
|
||||
);
|
||||
|
||||
this._renderDatePicker();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:layerType', function () {
|
||||
this.dateModel.set('readOnly', this.model.get('layerType') === 'night');
|
||||
this.render();
|
||||
}, this);
|
||||
this.dateModel.bind('change:value', function () {
|
||||
var date = moment(this.dateModel.get('value')).format(this.options.dateFormat);
|
||||
this.model.set('date', date);
|
||||
}, this);
|
||||
this.add_related_model(this.dateModel);
|
||||
},
|
||||
|
||||
_renderDatePicker: function () {
|
||||
// Date picker
|
||||
var datepicker = this.datepicker = new DatePickerView({
|
||||
className: 'DatePicker DatePicker--withBorder',
|
||||
model: this.dateModel
|
||||
});
|
||||
this._$datePicker().html(datepicker.render().el);
|
||||
this.addView(datepicker);
|
||||
|
||||
// Disabled tooltip
|
||||
if (this.dateModel.get('readOnly')) {
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: this._$datePicker(),
|
||||
title: function (e) {
|
||||
return $(this).attr('data-title');
|
||||
}
|
||||
});
|
||||
this.addView(tooltip);
|
||||
}
|
||||
},
|
||||
|
||||
_onChangeToNight: function () {
|
||||
this.model.set('layerType', 'night');
|
||||
},
|
||||
|
||||
_onChangeToDay: function () {
|
||||
this.model.set('layerType', 'day');
|
||||
},
|
||||
|
||||
_$datePicker: function () {
|
||||
return this.$('.js-datePicker');
|
||||
},
|
||||
|
||||
_updateOkBtn: function () {
|
||||
this._submitButton.find('span').text(_t('components.modals.add-basemap.add-btn'));
|
||||
},
|
||||
|
||||
_disableOkBtn: function (disable) {
|
||||
this._submitButton.toggleClass('is-disabled', disable);
|
||||
}
|
||||
|
||||
});
|
||||
22
lib/assets/javascripts/builder/components/modals/add-basemap/nasa/nasa.tpl
Executable file
@@ -0,0 +1,22 @@
|
||||
<div class="u-flex u-alignCenter Modal-basemapContainer">
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor Modal-titleBasemap"><%- _t('components.modals.add-basemap.nasa.select') %></h3>
|
||||
<div class="CDB-Text u-flex u-alignCenter">
|
||||
<div class="Form-rowData Form-rowData--short Form-rowData--alignLeft">
|
||||
<div class="Form-rowData Form-rowData--full">
|
||||
<div class="RadioButton js-day">
|
||||
<button type="button" class="RadioButton-input <% if (layerType === 'day') { %>is-checked<% } %>"></button>
|
||||
<label class="Metadata-label Metadata-label--auto CDB-Text CDB-Size-small is-semibold u-upperCase u-ellipsis" for="nasa-type-day"><%- _t('components.modals.add-basemap.nasa.day') %></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--full">
|
||||
<div class="RadioButton js-night">
|
||||
<button type="button" class="RadioButton-input <% if (layerType === 'night') { %>is-checked<% } %>"/></button>
|
||||
<label class="Metadata-label Metadata-label--auto CDB-Text CDB-Size-small is-semibold u-upperCase u-ellipsis" for="nasa-type-night"><%- _t('components.modals.add-basemap.nasa.night') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--short">
|
||||
<div class="js-datePicker" data-title="<%- _t('components.modals.add-basemap.nasa.cant-select') %>"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
81
lib/assets/javascripts/builder/components/modals/add-basemap/tabs-view.js
Executable file
@@ -0,0 +1,81 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var $ = require('jquery');
|
||||
var template = require('./tabs.tpl');
|
||||
|
||||
/**
|
||||
* View representing the tabs content of the dialog.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-tabs button': '_onClickTab'
|
||||
},
|
||||
|
||||
attributes: function () {
|
||||
return {
|
||||
class: 'Modal-outer'
|
||||
};
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
if (!opts.modalFooter) throw new Error('modalFooter is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this._modalFooter = opts.modalFooter;
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template({
|
||||
model: this.model
|
||||
}));
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var scrollView = new ScrollView({
|
||||
className: 'ScrollView ScrollView--withoutMargins',
|
||||
createContentView: function () {
|
||||
return this._createTabContentView();
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
this.addView(scrollView);
|
||||
this.$('.js-tab-content').append(scrollView.render().el);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:currentTab', this.render, this);
|
||||
},
|
||||
|
||||
_createTabContentView: function () {
|
||||
if (this._currentTabView) {
|
||||
this._currentTabView.clean();
|
||||
}
|
||||
this._currentTabView = this.model.activeTabModel().createView({
|
||||
submitButton: this._submitButton,
|
||||
modalFooter: this._modalFooter
|
||||
});
|
||||
this.addView(this._currentTabView);
|
||||
return this._currentTabView.render();
|
||||
},
|
||||
|
||||
_onClickTab: function (e) {
|
||||
this.killEvent(e);
|
||||
var name = $(e.target).closest('button').data('name');
|
||||
if (name) {
|
||||
this.model.set('currentTab', name);
|
||||
} else {
|
||||
throw new Error('tab name was expected but was empty');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
12
lib/assets/javascripts/builder/components/modals/add-basemap/tabs.tpl
Executable file
@@ -0,0 +1,12 @@
|
||||
<div class="Modal-navigation">
|
||||
<ul class="Modal-navigationInner CDB-Text is-semibold CDB-Size-medium js-tabs">
|
||||
<% model.get('tabs').each(function(tab) { %>
|
||||
<li class="CDB-NavMenu-item <%- model.get('currentTab') === tab.get('name') ? 'is-selected' : '' %>">
|
||||
<button data-name="<%- tab.get('name') %>" class="CDB-NavMenu-link u-upperCase">
|
||||
<%- tab.get('label') %>
|
||||
</button>
|
||||
</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="Modal-inner Modal-inner--with-navigation js-tab-content"></div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="u-flex u-alignCenter Modal-basemapContainer">
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor Modal-titleBasemap"><%- _t('components.modals.add-basemap.tilejson.insert') %></h3>
|
||||
<div class="CDB-Text u-flex u-alignCenter">
|
||||
<label class="Metadata-label Metadata-label--auto CDB-Text CDB-Size-small is-semibold u-upperCase u-ellipsis"><%- _t('components.modals.add-basemap.xyz.enter') %></label>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<input type="text" class="has-icon CDB-InputText CDB-Text js-url" value="" placeholder="<%- _t('components.modals.add-basemap.xyz.eg') %> http://domain.com/tiles.json?foo=bar">
|
||||
<i class="CDB-IconFont CDB-IconFont-dribbble Form-inputIcon js-idle"></i>
|
||||
<i class="Spinner XYZPanel-inputIcon Spinner--formIcon Form-inputIcon js-validating" style="display: none;"></i>
|
||||
<div class="XYZPanel-error CDB-InfoTooltip CDB-InfoTooltip--left is-error CDB-Text CDB-Size-medium CDB-InfoTooltip-text js-error"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
var Backbone = require('backbone');
|
||||
var CustomBaselayerModel = require('builder/data/custom-baselayer-model');
|
||||
|
||||
/**
|
||||
* Model to representing a TileJSON endpoint
|
||||
* See https://github.com/mapbox/tilejson-spec/tree/master/2.1.0 for details
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
url: function () {
|
||||
return this.get('tilejson_url');
|
||||
},
|
||||
|
||||
newTileLayer: function () {
|
||||
if (!this._isFetched()) throw new Error('no tiles, have fetch been called and returned a successful resultset?');
|
||||
|
||||
var url = this._urlTemplate();
|
||||
|
||||
var layer = new CustomBaselayerModel({
|
||||
urlTemplate: url,
|
||||
attribution: this.get('attribution'),
|
||||
maxZoom: this.get('maxzoom'),
|
||||
minZoom: this.get('minzoom'),
|
||||
name: this._name(),
|
||||
bounding_boxes: this.get('bounds'),
|
||||
tms: this.get('scheme') === 'tms',
|
||||
category: 'TileJSON',
|
||||
type: 'Tiled'
|
||||
});
|
||||
layer.set('className', layer._generateClassName(url));
|
||||
|
||||
return layer;
|
||||
},
|
||||
|
||||
setUrl: function (url) {
|
||||
this.set('tilejson_url', url);
|
||||
},
|
||||
|
||||
_isFetched: function () {
|
||||
return this.get('tiles').length > 0;
|
||||
},
|
||||
|
||||
_urlTemplate: function () {
|
||||
return this.get('tiles')[0];
|
||||
},
|
||||
|
||||
_name: function () {
|
||||
return this.get('name') || this.get('description');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var TileJSONView = require('./tilejson-view');
|
||||
var TileJSONLayerModel = require('./tilejson-layer-model');
|
||||
|
||||
/**
|
||||
* View model for TileJSON tab content.
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
name: 'tilejson',
|
||||
label: 'TileJSON',
|
||||
layer: undefined // will be set when valid
|
||||
},
|
||||
|
||||
createView: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
|
||||
var tileJSONLayerModel = new TileJSONLayerModel();
|
||||
|
||||
return new TileJSONView({
|
||||
model: this,
|
||||
submitButton: this._submitButton,
|
||||
tileJSONLayerModel: tileJSONLayerModel
|
||||
});
|
||||
},
|
||||
|
||||
hasAlreadyAddedLayer: function (userLayers) {
|
||||
var urlTemplate = this.get('layer').get('urlTemplate');
|
||||
return _.any(userLayers.isCustomCategory(), function (customLayer) {
|
||||
return customLayer.get('urlTemplate') === urlTemplate;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./enter-url.tpl');
|
||||
|
||||
/**
|
||||
* Represents the TileJSON tab content.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'keydown .js-url': '_onKeydown',
|
||||
'paste .js-url': '_onPaste'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
if (!opts.tileJSONLayerModel) throw new Error('tileJSONLayerModel is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this._tileJSONLayerModel = opts.tileJSONLayerModel;
|
||||
this._lastURL = '';
|
||||
this._debouncedUpdate = _.debounce(this._update.bind(this), 150);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this._updateOkBtn();
|
||||
this._disableOkBtn(true);
|
||||
|
||||
this.$el.html(
|
||||
template()
|
||||
);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onKeydown: function (e) {
|
||||
e.stopPropagation();
|
||||
this._debouncedUpdate();
|
||||
},
|
||||
|
||||
_onPaste: function (e) {
|
||||
e.stopPropagation();
|
||||
this._debouncedUpdate();
|
||||
},
|
||||
|
||||
_update: function () {
|
||||
var self = this;
|
||||
|
||||
this._disableOkBtn(true);
|
||||
this._indicateIsValidating(true);
|
||||
|
||||
var url = this._urlWithHTTP();
|
||||
|
||||
if (url === this._lastURL) {
|
||||
// Even if triggered nothing really changed so just update UI and return early
|
||||
this._indicateIsValidating(false);
|
||||
this._updateError();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastURL = url;
|
||||
|
||||
this._indicateIsValidating(true);
|
||||
|
||||
this._tileJSONLayerModel.setUrl(url);
|
||||
|
||||
this._tileJSONLayerModel.fetch({
|
||||
success: function (mdl) {
|
||||
if (url === self._lastURL) {
|
||||
self.model.set('layer', mdl.newTileLayer());
|
||||
self._disableOkBtn(false);
|
||||
self._indicateIsValidating(false);
|
||||
self._updateError();
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
if (url === self._lastURL) {
|
||||
self._indicateIsValidating(false);
|
||||
// Note that this text can not be longer, or it will exceed available space of the error label.
|
||||
self._updateError(_t('components.modals.add-basemap.tilejson.invalid'));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_updateOkBtn: function () {
|
||||
this._submitButton.find('span').text(_t('components.modals.add-basemap.add-btn'));
|
||||
},
|
||||
|
||||
_disableOkBtn: function (disable) {
|
||||
this._submitButton.toggleClass('is-disabled', disable);
|
||||
},
|
||||
|
||||
_updateError: function (msg) {
|
||||
this.$('.js-error').text(msg)[msg ? 'addClass' : 'removeClass']('is-visible');
|
||||
},
|
||||
|
||||
_indicateIsValidating: function (indicate) {
|
||||
if (indicate) {
|
||||
this.$('.js-idle').hide();
|
||||
this.$('.js-validating').show();
|
||||
} else {
|
||||
this.$('.js-validating').hide();
|
||||
this.$('.js-idle').show();
|
||||
}
|
||||
},
|
||||
|
||||
// So don't try to be fetched relatively to current URL path later
|
||||
_urlWithHTTP: function () {
|
||||
var str = this.$('.js-url').val();
|
||||
|
||||
if (str.indexOf('http://') === -1 && str.indexOf('https://') === -1) {
|
||||
return 'http://' + str;
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="u-flex u-alignCenter Modal-basemapContainer">
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor Modal-titleBasemap"><%- _t('components.modals.add-basemap.wms.insert') %></h3>
|
||||
<div class="CDB-Text u-flex u-alignCenter">
|
||||
<label class="Metadata-label Metadata-label--auto CDB-Text CDB-Size-small is-semibold u-upperCase u-ellipsis"><%- _t('components.modals.add-basemap.xyz.enter') %></label>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<input type="text" class="has-icon CDB-InputText CDB-Text js-url" value="" placeholder="<%- _t('components.modals.add-basemap.xyz.eg') %> http://openlayers.org/en/v3.5.0/examples/data/ogcsample.xml">
|
||||
<i class="CDB-IconFont CDB-IconFont-dribbble Form-inputIcon js-idle"></i>
|
||||
<i class="Spinner XYZPanel-inputIcon XYZPanel-inputIcon--loader Spinner--formIcon Form-inputIcon js-validating" style="display: none;"></i>
|
||||
<div class="XYZPanel-error CDB-InfoTooltip CDB-InfoTooltip--left is-error CDB-Text CDB-Size-medium CDB-InfoTooltip-text js-error <%- (layersFetched && layers.length === 0) ? 'is-visible' : '' %>">
|
||||
<% if (layersFetched && layers.length === 0) { %>
|
||||
<%- _t('components.modals.add-basemap.wms.invalid') %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./select-layer.tpl');
|
||||
var WMSLayersView = require('./wms-layers-view');
|
||||
|
||||
/**
|
||||
* Sub view, to select what layer to use as basemap.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-back': '_onClickBack'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
searchQuery: this.model.get('searchQuery'),
|
||||
layersFound: this.model.getLayers(),
|
||||
layersAvailableCount: this.model.layersAvailableCount()
|
||||
})
|
||||
);
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var wmsListView = new WMSLayersView({
|
||||
model: this.model,
|
||||
customBaselayersCollection: this._customBaselayersCollection
|
||||
});
|
||||
|
||||
this.addView(wmsListView);
|
||||
this.$('.js-layers').append(wmsListView.render().el);
|
||||
},
|
||||
|
||||
_onClickBack: function (e) {
|
||||
this.killEvent(e);
|
||||
this.model.set('currentView', 'enterURL');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
<div class="u-inner">
|
||||
<div class="Filters WMSSSelectLayer-Filter is-relative">
|
||||
<div class="Filters-inner">
|
||||
<div class="Filters-row">
|
||||
<div class="Filters-group">
|
||||
<div class="Filters-typeItem Filters-typeItem--searchEnabler">
|
||||
<button class="Filters-searchLink CDB-Text is-semibold u-upperCase CDB-Size-medium js-search-link">
|
||||
<i class="Filters-searchLinkIcon CDB-IconFont CDB-IconFont-lens"></i><%- _t('components.modals.add-layer.navigation.search') %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Filters-typeItem Filters-typeItem--searchField">
|
||||
<form class="Filters-searchForm js-search-form" action="#">
|
||||
<input class="Filters-searchInput CDB-Text CDB-Size-medium js-search-input" type="text" value="<%- searchQuery %>" placeholder="<%- _t('components.modals.add-basemap.wms.placeholder', { layersFoundCount: layersFound.length, layersFoundCountPluralize: _t('components.modals.add-basemap.wms.tables-pluralize', { smart_count: layersFound.length }), layersAvailableCount: layersAvailableCount, layersAvailableCountPluralize: _t('components.modals.add-basemap.wms.tables-pluralize', { smart_count: layersAvailableCount }) }) %>" />
|
||||
<button type="button" class="Filters-cleanSearch js-clean-search u-actionTextColor">
|
||||
<i class="CDB-IconFont CDB-IconFont-close"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<span class="Filters-separator"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (searchQuery && layersFound.length == 0) { %>
|
||||
<div class="IntermediateInfo">
|
||||
<div class="LayoutIcon">
|
||||
<i class="CDB-IconFont CDB-IconFont-defaultUser"></i>
|
||||
</div>
|
||||
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-bSpace u-secondaryTextColor u-tSpace-xl"><%- _t('components.modals.add-basemap.wms.oh-no') %></h4>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor"><%- _t('components.modals.add-basemap.wms.unfortunately') %></p>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="js-layers"></div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<button class="NavButton Dialog-backBtn js-back">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev"></i>
|
||||
</button>
|
||||
@@ -0,0 +1,163 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CustomBaselayerModel = require('builder/data/custom-baselayer-model');
|
||||
|
||||
/**
|
||||
* Model for an individual WMS/WMTS layer.
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
state: 'idle' // idle, saving, saveDone, saveFail
|
||||
},
|
||||
|
||||
url: function () {
|
||||
return this._wmsService.saveLayerURL({
|
||||
title: this.get('title'),
|
||||
name: this.get('name'),
|
||||
layer: this.get('name'),
|
||||
srs: this.get('srs'),
|
||||
bounding_boxes: this.get('llbbox'),
|
||||
type: this.get('type'), // wms/wmts
|
||||
matrix_sets: this.get('matrix_sets')
|
||||
});
|
||||
},
|
||||
|
||||
sync: function (method, model, options) {
|
||||
options = options || {};
|
||||
options.url = this.url(method.toLowerCase());
|
||||
options.dataType = 'jsonp';
|
||||
options.attrs = '_';
|
||||
method = 'READ';
|
||||
|
||||
return Backbone.sync.apply(this, arguments);
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.wmsService) throw new Error('wmsService is required');
|
||||
|
||||
this._wmsService = opts.wmsService;
|
||||
},
|
||||
|
||||
canSave: function (customBaselayersCollection) {
|
||||
return !_.any(customBaselayersCollection.isCustomCategory(), function (customLayer) {
|
||||
return customLayer.get('name') === this.get('title');
|
||||
}, this);
|
||||
},
|
||||
|
||||
createProxiedLayerOrCustomBaselayerModel: function () {
|
||||
this.set('state', 'saving');
|
||||
this._shouldBeProxied() ? this._createProxiedLayer() : this._newCustomBaselayerModel();
|
||||
},
|
||||
|
||||
_shouldBeProxied: function () {
|
||||
if (this.get('type') === 'wmts') {
|
||||
var supportedMatrixSets = this._wmsService.supportedMatrixSets(this.get('matrix_sets') || []);
|
||||
|
||||
return supportedMatrixSets.length > 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_createProxiedLayer: function () {
|
||||
var self = this;
|
||||
|
||||
this.save({}, {
|
||||
success: function () {
|
||||
var proxiedBaselayerModel;
|
||||
|
||||
try {
|
||||
proxiedBaselayerModel = self._newProxiedBaselayerModel();
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if (proxiedBaselayerModel) {
|
||||
self._setCustomBaselayerModel(proxiedBaselayerModel);
|
||||
} else {
|
||||
self.set('state', 'saveFail');
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
self.set('state', 'saveFail');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_newCustomBaselayerModel: function () {
|
||||
var customBaselayerModel = this._byCustomURL(this._xyzURLTemplate());
|
||||
|
||||
customBaselayerModel.set({
|
||||
name: this.get('title') || this.get('name'),
|
||||
attribution: this.get('attribution'),
|
||||
bounding_boxes: this.get('llbbox')
|
||||
});
|
||||
this._setCustomBaselayerModel(customBaselayerModel);
|
||||
|
||||
return customBaselayerModel;
|
||||
},
|
||||
|
||||
_newProxiedBaselayerModel: function () {
|
||||
if (!this.get('mapproxy_id')) {
|
||||
throw new Error('mapproxy_id must be set');
|
||||
}
|
||||
|
||||
var url = this._wmsService.getProxyTilesURL() + '/' + this.get('mapproxy_id') + '/wmts/map/webmercator/{z}/{x}/{y}.png';
|
||||
var proxiedBaselayerModel = this._generateCustomBaselayerModel(url);
|
||||
|
||||
proxiedBaselayerModel.set({
|
||||
name: this.get('title') || this.get('name'),
|
||||
attribution: this.get('attribution'),
|
||||
proxy: true,
|
||||
bounding_boxes: this.get('bounding_boxes')
|
||||
});
|
||||
|
||||
return proxiedBaselayerModel;
|
||||
},
|
||||
|
||||
_generateCustomBaselayerModel: function (url) {
|
||||
var layer = new CustomBaselayerModel({
|
||||
urlTemplate: url,
|
||||
attribution: null,
|
||||
maxZoom: 21,
|
||||
minZoom: 0,
|
||||
name: '',
|
||||
category: 'WMS',
|
||||
tms: false,
|
||||
type: 'Tiled'
|
||||
});
|
||||
layer.set('className', layer._generateClassName(url));
|
||||
|
||||
return layer;
|
||||
},
|
||||
|
||||
_byCustomURL: function (url) {
|
||||
// Minimal test for "valid URL" w/o having to complicate it with regex
|
||||
if (url && url.indexOf('/') === -1) throw new TypeError('invalid URL');
|
||||
|
||||
// Only lowercase the placeholder variables, since the URL may contain case-sensitive data (e.g. API keys and such)
|
||||
url = url.replace(/\{S\}/g, '{s}')
|
||||
.replace(/\{X\}/g, '{x}')
|
||||
.replace(/\{Y\}/g, '{y}')
|
||||
.replace(/\{Z\}/g, '{z}');
|
||||
|
||||
var layer = this._generateCustomBaselayerModel(url);
|
||||
|
||||
return layer;
|
||||
},
|
||||
|
||||
_xyzURLTemplate: function () {
|
||||
var urlTemplate = this.get('url_template') || '';
|
||||
// Convert the proxy template variables to XYZ format, http://foo.com/bar/%%(z)s/%%(x)s/%%(y)s.png"
|
||||
return urlTemplate.replace(/%%\((\w)\)s/g, '{$1}');
|
||||
},
|
||||
|
||||
_setCustomBaselayerModel: function (customBaselayerModel) {
|
||||
this.set({
|
||||
state: 'saveDone',
|
||||
customBaselayerModel: customBaselayerModel
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./wms-layer.tpl');
|
||||
|
||||
/**
|
||||
* View for an individual layer item.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
className: 'List-row',
|
||||
|
||||
events: {
|
||||
'click .js-add': '_onClickAdd'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
model: this.model,
|
||||
canSave: this.model.canSave(this._customBaselayersCollection)
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_onClickAdd: function (e) {
|
||||
this.killEvent(e);
|
||||
|
||||
if (this.model.canSave(this._customBaselayersCollection)) {
|
||||
this.model.createProxiedLayerOrCustomBaselayerModel();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="List-rowItem">
|
||||
<div class="DefaultTitle <%- canSave ? '': 'is-disabled' %>"><%- model.get('title') || model.get('name') %></div>
|
||||
<button class="js-add Button Button--secondary Button--secondaryTransparentBkg <%- canSave ? '' : 'is-disabled' %>">
|
||||
<span>Add this</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,46 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var WMSLayerModel = require('./wms-layer-model');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
|
||||
model: function (attrs, opts) {
|
||||
var self = opts.collection;
|
||||
|
||||
return new WMSLayerModel(attrs, {
|
||||
wmsService: self._wmsService
|
||||
});
|
||||
},
|
||||
|
||||
parse: function (r) {
|
||||
var layers = [];
|
||||
|
||||
if (r.layers) {
|
||||
layers = _.map(r.layers, function (layer) {
|
||||
return _.extend({ type: r.type || 'wms' }, layer);
|
||||
});
|
||||
}
|
||||
|
||||
return layers;
|
||||
},
|
||||
|
||||
sync: function (method, model, options) {
|
||||
options = options || {};
|
||||
options.url = this.url(method.toLowerCase());
|
||||
options.dataType = 'jsonp';
|
||||
method = 'READ';
|
||||
|
||||
return Backbone.sync.apply(this, arguments);
|
||||
},
|
||||
|
||||
url: function () {
|
||||
return this._wmsService.getFetchLayersURL();
|
||||
},
|
||||
|
||||
initialize: function (models, opts) {
|
||||
if (!opts.wmsService) throw new Error('wmsService is required');
|
||||
|
||||
this._wmsService = opts.wmsService;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
var WMSLayerView = require('./wms-layer-view');
|
||||
var CoreView = require('backbone/core-view');
|
||||
|
||||
/**
|
||||
* Sub view, to select what layer to use as basemap.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'List',
|
||||
tagName: 'ul',
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
if (!this.model) throw new Error('model is required');
|
||||
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.append.apply(this.$el, this._renderedLayers());
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderedLayers: function () {
|
||||
return this.model.getLayers().map(function (layer) {
|
||||
var view = new WMSLayerView({
|
||||
model: layer,
|
||||
customBaselayersCollection: this._customBaselayersCollection
|
||||
});
|
||||
|
||||
this.addView(view);
|
||||
|
||||
return view.render().el;
|
||||
}, this);
|
||||
}
|
||||
|
||||
});
|
||||
128
lib/assets/javascripts/builder/components/modals/add-basemap/wms/wms-model.js
Executable file
@@ -0,0 +1,128 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var WMSView = require('./wms-view');
|
||||
var WMSLayersCollection = require('./wms-layers-collection');
|
||||
var WMSService = require('builder/data/wms-service');
|
||||
|
||||
/**
|
||||
* View model for WMS/WMTS tab content.
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
name: 'wms',
|
||||
label: 'WMS/WMTS',
|
||||
currentView: 'enterURL', // [fetchingLayers, selectLayer, savingLayer]
|
||||
layersFetched: false,
|
||||
layer: undefined // will be set when selected
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
|
||||
this.wmsService = new WMSService();
|
||||
this.wmsLayersCollection = new WMSLayersCollection(null, {
|
||||
wmsService: this.wmsService
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.wmsLayersCollection.bind('change:state', this._onLayerStateChange, this);
|
||||
this.wmsLayersCollection.bind('reset', function () {
|
||||
this.set({
|
||||
currentView: this.wmsLayersCollection.length > 0 ? 'selectLayer' : 'enterURL',
|
||||
layersFetched: true
|
||||
});
|
||||
|
||||
this.trigger('layersFetched');
|
||||
}, this);
|
||||
},
|
||||
|
||||
createView: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
if (!opts.modalFooter) throw new Error('modalFooter is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this._modalFooter = opts.modalFooter;
|
||||
|
||||
this.set({
|
||||
currentView: 'enterURL',
|
||||
layersFetched: false
|
||||
});
|
||||
|
||||
return new WMSView({
|
||||
model: this,
|
||||
customBaselayersCollection: this._customBaselayersCollection,
|
||||
submitButton: this._submitButton,
|
||||
modalFooter: this._modalFooter
|
||||
});
|
||||
},
|
||||
|
||||
fetchLayers: function (url) {
|
||||
this.set('currentView', 'fetchingLayers');
|
||||
|
||||
this.wmsService.setUrl(url);
|
||||
|
||||
this.wmsLayersCollection.fetch({
|
||||
reset: true
|
||||
});
|
||||
},
|
||||
|
||||
layersAvailableCount: function () {
|
||||
return _.difference(
|
||||
this.wmsLayersCollection.pluck('title'),
|
||||
this._customBaselayersCollection.pluck('name')
|
||||
).length;
|
||||
},
|
||||
|
||||
get: function (name) {
|
||||
if (name === 'layer') {
|
||||
var customBaselayerModel = this.wmsLayersCollection.find(function (mdl) {
|
||||
return mdl.get('state') === 'saveDone';
|
||||
});
|
||||
|
||||
return customBaselayerModel && customBaselayerModel.get('customBaselayerModel');
|
||||
} else {
|
||||
return Backbone.Model.prototype.get.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
getLayers: function () {
|
||||
if (this.get('searchQuery')) {
|
||||
var regExp = new RegExp(this.get('searchQuery'), 'i');
|
||||
|
||||
return this.wmsLayersCollection.filter(function (layer) {
|
||||
return layer.get('name').match(regExp);
|
||||
}, this);
|
||||
} else {
|
||||
return this.wmsLayersCollection;
|
||||
}
|
||||
},
|
||||
|
||||
hasAlreadyAddedLayer: function () {
|
||||
// Already added layers are disabled to be saved for each layer
|
||||
return false;
|
||||
},
|
||||
|
||||
_onLayerStateChange: function (mdl, newState) {
|
||||
switch (newState) {
|
||||
case 'saving':
|
||||
this.set('currentView', 'savingLayer');
|
||||
break;
|
||||
case 'saveDone':
|
||||
this.set('layer', mdl.get('customBaselayerModel'));
|
||||
this.trigger('saveBasemap');
|
||||
break;
|
||||
case 'saveFail':
|
||||
this.set('currentView', 'saveFail');
|
||||
break;
|
||||
default:
|
||||
this.set('currentView', 'selectLayer');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
198
lib/assets/javascripts/builder/components/modals/add-basemap/wms/wms-view.js
Executable file
@@ -0,0 +1,198 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var SelectLayerView = require('./select-layer-view');
|
||||
var ViewFactory = require('builder/components/view-factory');
|
||||
var ErrorView = require('builder/components/error/error-view');
|
||||
var renderLoading = require('builder/components/loading/render-loading');
|
||||
var enterUrl = require('./enter-url.tpl');
|
||||
var $ = require('jquery');
|
||||
|
||||
/**
|
||||
* Represents the WMS/WMTS tab category.
|
||||
* Current state is defined by presence (or lack of) layers
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'keydown .js-search-input': '_onKeyDown',
|
||||
'submit .js-search-form': 'killEvent',
|
||||
'keydown .js-url': '_onKeydown',
|
||||
'paste .js-url': '_onPaste',
|
||||
'click .js-clean-search': '_onCleanSearchClick',
|
||||
'click .js-search-link': '_submitSearch'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
if (!opts.modalFooter) throw new Error('modalFooter is required');
|
||||
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
this._submitButton = opts.submitButton;
|
||||
this._modalFooter = opts.modalFooter;
|
||||
this._debouncedUpdate = _.debounce(this._update.bind(this), 150);
|
||||
this._onClickBinded = this._onClickOK.bind(this);
|
||||
|
||||
this._bindSubmitButton();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this._updateOkBtn();
|
||||
this._disableOkBtn(true);
|
||||
|
||||
var view;
|
||||
|
||||
switch (this.model.get('currentView')) {
|
||||
case 'savingLayer':
|
||||
this._disableModalFooter(true);
|
||||
|
||||
view = ViewFactory.createByHTML(
|
||||
renderLoading({
|
||||
title: _t('components.modals.add-basemap.saving')
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'selectLayer':
|
||||
this._disableModalFooter(true);
|
||||
|
||||
view = new SelectLayerView({
|
||||
model: this.model,
|
||||
customBaselayersCollection: this._customBaselayersCollection
|
||||
});
|
||||
break;
|
||||
case 'saveFail':
|
||||
this._disableModalFooter(true);
|
||||
|
||||
view = new ErrorView({
|
||||
title: _t('components.modals.add-basemap.add-basemap-error')
|
||||
});
|
||||
break;
|
||||
case 'fetchingLayers':
|
||||
this._disableModalFooter(true);
|
||||
|
||||
view = ViewFactory.createByHTML(
|
||||
renderLoading({
|
||||
title: _t('components.modals.add-basemap.fetching')
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'enterURL':
|
||||
default:
|
||||
this._disableModalFooter(false);
|
||||
|
||||
view = ViewFactory.createByHTML(
|
||||
enterUrl({
|
||||
layersFetched: this.model.get('layersFetched'),
|
||||
layers: this.model.wmsLayersCollection
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
|
||||
this.$('.js-search-input').focus();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_showCleanSearchButton: function () {
|
||||
this.$('.js-clean-search').show();
|
||||
},
|
||||
|
||||
_hideCleanSearchButton: function () {
|
||||
this.$('.js-clean-search').hide();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this.render, this);
|
||||
this.model.bind('change', this._onChangeSearchQuery, this);
|
||||
this.model.bind('layersFetched', this.render, this);
|
||||
},
|
||||
|
||||
_onKeydown: function (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
this._debouncedUpdate();
|
||||
},
|
||||
|
||||
_onPaste: function (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
this._debouncedUpdate();
|
||||
},
|
||||
|
||||
_update: function (e) {
|
||||
this._disableOkBtn(!this.$('.js-url').val());
|
||||
this.$('.js-error').removeClass('is-visible'); // resets error state when changed
|
||||
},
|
||||
|
||||
_disableOkBtn: function (disable) {
|
||||
this._submitButton.toggleClass('is-disabled', disable);
|
||||
},
|
||||
|
||||
_onKeyDown: function (e) {
|
||||
var enterPressed = (e.keyCode === $.ui.keyCode.ENTER);
|
||||
|
||||
if (enterPressed) {
|
||||
this.killEvent(e);
|
||||
|
||||
this._submitSearch();
|
||||
}
|
||||
},
|
||||
|
||||
_submitSearch: function (e) {
|
||||
this.killEvent(e);
|
||||
|
||||
this.model.set('searchQuery', this.$('.js-search-input').val());
|
||||
},
|
||||
|
||||
_onChangeSearchQuery: function () {
|
||||
var searchQuery = this.model.get('searchQuery');
|
||||
|
||||
if (!searchQuery) {
|
||||
this._hideCleanSearchButton();
|
||||
}
|
||||
},
|
||||
|
||||
_onCleanSearchClick: function (e) {
|
||||
this.killEvent(e);
|
||||
|
||||
this.model.set('searchQuery', '');
|
||||
},
|
||||
|
||||
_onClickOK: function (e) {
|
||||
this.killEvent(e);
|
||||
|
||||
var url = this.$('.js-url').val();
|
||||
|
||||
if (url) {
|
||||
this.model.fetchLayers(url);
|
||||
}
|
||||
},
|
||||
|
||||
_disableModalFooter: function (disable) {
|
||||
this._modalFooter.toggleClass('is-disabled', disable);
|
||||
},
|
||||
|
||||
_updateOkBtn: function () {
|
||||
this._submitButton.find('span').text(_t('components.modals.add-basemap.get-layers'));
|
||||
},
|
||||
|
||||
_bindSubmitButton: function () {
|
||||
this._submitButton.on('click', this._onClickBinded);
|
||||
},
|
||||
|
||||
_unBindSubmitButton: function () {
|
||||
this._submitButton.off('click', this._onClickBinded);
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._unBindSubmitButton();
|
||||
CoreView.prototype.clean.call(this);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="u-flex u-alignCenter Modal-basemapContainer">
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor Modal-titleBasemap"><%- _t('components.modals.add-basemap.xyz.insert') %></h3>
|
||||
<div class="CDB-Text u-flex u-alignCenter">
|
||||
<label class="Metadata-label Metadata-label--auto CDB-Text CDB-Size-small is-semibold u-upperCase u-ellipsis"><%- _t('components.modals.add-basemap.xyz.enter') %></label>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<input type="text" class="has-icon CDB-InputText CDB-Text js-url" value="" placeholder="E.g. https://{s}.carto.com/foobar/{z}/{x}/{y}.png">
|
||||
<i class="Spinner XYZPanel-inputIcon XYZPanel-inputIcon--loader Spinner--formIcon Form-inputIcon js-validating" style="display: none;"></i>
|
||||
<div class="Checkbox XYZPanel-inputCheckbox js-tms" data-title="Inverts Y axis numbering for tiles">
|
||||
<button class="Checkbox-input u-rSpace--m"></button>
|
||||
<label class="CDB-Text CDB-Size-small is-semibold u-upperCase"><%- _t('components.modals.add-basemap.xyz.tms') %></label>
|
||||
</div>
|
||||
<div class="XYZPanel-error CDB-InfoTooltip CDB-InfoTooltip--left is-error CDB-Text CDB-Size-medium CDB-InfoTooltip-text js-error"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var XYZView = require('./xyz-view');
|
||||
|
||||
/**
|
||||
* View model for XYZ tab content.
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
name: 'xyz',
|
||||
label: 'XYZ',
|
||||
tms: false,
|
||||
layer: undefined // will be set when valid
|
||||
},
|
||||
|
||||
createView: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
if (!opts.modalFooter) throw new Error('modalFooter is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this._modalFooter = opts.modalFooter;
|
||||
|
||||
return new XYZView({
|
||||
model: this,
|
||||
submitButton: this._submitButton,
|
||||
modalFooter: this._modalFooter
|
||||
});
|
||||
},
|
||||
|
||||
hasAlreadyAddedLayer: function (userLayers) {
|
||||
var urlTemplate = this.get('layer').get('urlTemplate');
|
||||
return _.any(userLayers.isCustomCategory(), function (customLayer) {
|
||||
return customLayer.get('urlTemplate') === urlTemplate;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
177
lib/assets/javascripts/builder/components/modals/add-basemap/xyz/xyz-view.js
Executable file
@@ -0,0 +1,177 @@
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./enter-url.tpl');
|
||||
var CustomBaselayerModel = require('builder/data/custom-baselayer-model');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
|
||||
/**
|
||||
* Represents the XYZ tab content.
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'XYZPanel',
|
||||
|
||||
events: {
|
||||
'click .js-tms': '_changeTMS',
|
||||
'keydown .js-url': '_onKeydown',
|
||||
'paste .js-url': '_onPaste'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.submitButton) throw new Error('submitButton is required');
|
||||
|
||||
this._submitButton = opts.submitButton;
|
||||
this._lastCallSeq = 0;
|
||||
this._debouncedUpdate = _.debounce(this._update.bind(this), 150);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this._updateOkBtn();
|
||||
this._disableOkBtn(true);
|
||||
|
||||
this.$el.html(
|
||||
template()
|
||||
);
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
// Add TMS tooltip
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-tms'),
|
||||
title: function () {
|
||||
return $(this).data('title');
|
||||
}
|
||||
});
|
||||
this.addView(tooltip);
|
||||
},
|
||||
|
||||
_onKeydown: function (e) {
|
||||
e.stopPropagation();
|
||||
this._debouncedUpdate();
|
||||
},
|
||||
|
||||
_onPaste: function (e) {
|
||||
e.stopPropagation();
|
||||
this._debouncedUpdate();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:tms', this._setTMSCheckbox, this);
|
||||
},
|
||||
|
||||
_update: function () {
|
||||
this._disableOkBtn(true);
|
||||
this._indicateIsValidating(true);
|
||||
var layer;
|
||||
var urlErrorMsg;
|
||||
|
||||
var url = this.$('.js-url').val();
|
||||
var tms = this.model.get('tms');
|
||||
|
||||
if (url) {
|
||||
try {
|
||||
layer = this._byCustomURL(url, tms);
|
||||
} catch (e) {
|
||||
urlErrorMsg = _t('components.modals.add-basemap.xyz.not-valid');
|
||||
}
|
||||
}
|
||||
|
||||
this.model.set('layer', layer);
|
||||
|
||||
if (layer) {
|
||||
var self = this;
|
||||
// Make sure only the last call made is the one that defines view change,
|
||||
// avoids laggy responses to indicate wrong state
|
||||
var thisCallSeq = ++this._lastCallSeq;
|
||||
layer.validateTemplateURL({
|
||||
success: function () {
|
||||
if (thisCallSeq === self._lastCallSeq) {
|
||||
self._disableOkBtn(false);
|
||||
self._indicateIsValidating(false);
|
||||
self._updateError();
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
if (thisCallSeq === self._lastCallSeq) {
|
||||
self._disableOkBtn(false);
|
||||
self._indicateIsValidating(false);
|
||||
self._updateError(_t('components.modals.add-basemap.xyz.couldnt-validate'));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (url) {
|
||||
this._indicateIsValidating(false);
|
||||
this._updateError(urlErrorMsg);
|
||||
} else {
|
||||
this._indicateIsValidating(false);
|
||||
this._updateError();
|
||||
}
|
||||
},
|
||||
|
||||
_changeTMS: function (e) {
|
||||
this.model.set('tms', !this.model.get('tms'));
|
||||
this._onKeydown(e);
|
||||
},
|
||||
|
||||
_setTMSCheckbox: function (e) {
|
||||
this.$('.js-tms .Checkbox-input').toggleClass('is-checked', this.model.get('tms'));
|
||||
},
|
||||
|
||||
_byCustomURL: function (url, tms) {
|
||||
// Minimal test for "valid URL" w/o having to complicate it with regex
|
||||
if (url && url.indexOf('/') === -1) throw new TypeError('invalid URL');
|
||||
|
||||
// Only lowercase the placeholder variables, since the URL may contain case-sensitive data (e.g. API keys and such)
|
||||
url = url.replace(/\{S\}/g, '{s}')
|
||||
.replace(/\{X\}/g, '{x}')
|
||||
.replace(/\{Y\}/g, '{y}')
|
||||
.replace(/\{Z\}/g, '{z}');
|
||||
|
||||
var layer = new CustomBaselayerModel({
|
||||
urlTemplate: url,
|
||||
attribution: null,
|
||||
maxZoom: 21,
|
||||
minZoom: 0,
|
||||
name: '',
|
||||
tms: tms,
|
||||
category: 'Custom',
|
||||
type: 'Tiled'
|
||||
});
|
||||
layer.set('className', layer._generateClassName(url));
|
||||
|
||||
return layer;
|
||||
},
|
||||
|
||||
_setTMS: function (ev) {
|
||||
var $checkbox = $(ev.target).closest('.Checkbox');
|
||||
$checkbox.find('.Checkbox-input').toggleClass('is-checked');
|
||||
this._update(ev);
|
||||
},
|
||||
|
||||
_updateOkBtn: function () {
|
||||
this._submitButton.find('span').text(_t('components.modals.add-basemap.add-btn'));
|
||||
},
|
||||
|
||||
_disableOkBtn: function (disable) {
|
||||
this._submitButton.toggleClass('is-disabled', disable);
|
||||
},
|
||||
|
||||
_updateError: function (msg) {
|
||||
this.$('.js-error').text(msg)[ msg ? 'addClass' : 'removeClass' ]('is-visible');
|
||||
},
|
||||
|
||||
_indicateIsValidating: function (indicate) {
|
||||
this.$('.js-validating').toggle(!!indicate);
|
||||
}
|
||||
|
||||
});
|
||||
251
lib/assets/javascripts/builder/components/modals/add-layer/add-layer-model.js
Executable file
@@ -0,0 +1,251 @@
|
||||
var Backbone = require('backbone');
|
||||
var UploadModel = require('builder/data/upload-model');
|
||||
var VisualizationFetchModel = require('builder/data/visualizations-fetch-model');
|
||||
var TablesCollection = require('builder/data/visualizations-collection');
|
||||
var TableModel = require('builder/data/table-model');
|
||||
|
||||
var MetricsTracker = require('builder/components/metrics/metrics-tracker');
|
||||
var MetricsTypes = require('builder/components/metrics/metrics-types');
|
||||
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var IMPORT = 'import';
|
||||
var DATASETS = 'datasets';
|
||||
var SCRATCH = 'scratch';
|
||||
|
||||
var IMPORT_FILE = 'file';
|
||||
var IMPORT_TWITTER = 'twitter';
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'userActions',
|
||||
'configModel',
|
||||
'pollingModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Add layer model
|
||||
*
|
||||
* "Implements" the CreateListingModel.
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
type: 'addLayer',
|
||||
contentPane: 'listing', // [listing, loading]
|
||||
listing: DATASETS, // [IMPORT, DATASETS, SCRATCH]
|
||||
collectionFetched: false,
|
||||
activeImportPane: IMPORT_FILE
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initModels();
|
||||
this._initBinds();
|
||||
this._fetchCollection();
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this._uploadModel = new UploadModel({
|
||||
create_vis: false
|
||||
}, {
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._selectedDatasetsCollection = new Backbone.Collection();
|
||||
|
||||
this._tablesCollection = new TablesCollection([], {
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._visualizationFetchModel = new VisualizationFetchModel({
|
||||
content_type: DATASETS,
|
||||
library: this.showLibrary()
|
||||
});
|
||||
},
|
||||
|
||||
getTablesCollection: function () {
|
||||
return this._tablesCollection;
|
||||
},
|
||||
|
||||
getSelectedDatasetsCollection: function () {
|
||||
return this._selectedDatasetsCollection;
|
||||
},
|
||||
|
||||
getVisualizationFetchModel: function () {
|
||||
return this._visualizationFetchModel;
|
||||
},
|
||||
|
||||
getUploadModel: function () {
|
||||
return this._uploadModel;
|
||||
},
|
||||
|
||||
canSelect: function (dataset) {
|
||||
return dataset.get('selected') || this._selectedDatasetsCollection.length < 1; // for now only allow 1 item
|
||||
},
|
||||
|
||||
showLibrary: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
showDatasets: function () {
|
||||
return true;
|
||||
},
|
||||
|
||||
setActiveImportPane: function (name) {
|
||||
this.set('activeImportPane', name);
|
||||
},
|
||||
|
||||
canFinish: function () {
|
||||
if (this._atImportPane()) {
|
||||
return this._uploadModel.isValidToUpload();
|
||||
} else if (this._atDatasetsPane()) {
|
||||
return this._selectedDatasetsCollection.length > 0;
|
||||
}
|
||||
},
|
||||
|
||||
finish: function () {
|
||||
if (this._atImportPane()) {
|
||||
this._pollingModel.trigger('importByUploadData', this._uploadModel.toJSON(), this);
|
||||
} else if (this._atDatasetsPane()) {
|
||||
var mdl = this._selectedDatasetsCollection.at(0);
|
||||
if (mdl.get('type') === 'remote') {
|
||||
var d = {
|
||||
create_vis: false,
|
||||
type: 'remote',
|
||||
value: mdl.get('name'),
|
||||
remote_visualization_id: mdl.get('id'),
|
||||
size: mdl.get('external_source') ? mdl.get('external_source').size : undefined
|
||||
};
|
||||
// See BackgroundImporter where the same event is bound to be handled..
|
||||
this._pollingModel.trigger('importByUploadData', d, this);
|
||||
} else {
|
||||
this._addNewLayer(mdl.getTableModel());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getImportState: function () {
|
||||
return this.get('activeImportPane');
|
||||
},
|
||||
|
||||
showGuessingToggler: function () {
|
||||
return this._atImportPane();
|
||||
},
|
||||
|
||||
showPrivacyToggler: function () {
|
||||
var hiddenDueToDeprecation = this._atTwitterImportPane() && !this._userModel.hasOwnTwitterCredentials();
|
||||
var hasToBeShowed = this._atImportPane() && !hiddenDueToDeprecation;
|
||||
return hasToBeShowed;
|
||||
},
|
||||
|
||||
_atImportPane: function () {
|
||||
return this.get('listing') === IMPORT;
|
||||
},
|
||||
|
||||
_atDatasetsPane: function () {
|
||||
return this.get('listing') === DATASETS;
|
||||
},
|
||||
|
||||
_atScratchPane: function () {
|
||||
return this.get('listing') === SCRATCH;
|
||||
},
|
||||
|
||||
_atTwitterImportPane: function () {
|
||||
return this.get('activeImportPane') === IMPORT_TWITTER;
|
||||
},
|
||||
|
||||
createFromScratch: function () {
|
||||
var self = this;
|
||||
this.set('contentPane', 'creatingFromScratch');
|
||||
var tableModel = new TableModel({}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
tableModel.save({}, {
|
||||
success: function () {
|
||||
self._addNewLayer(tableModel, true);
|
||||
},
|
||||
error: function (req, resp) {
|
||||
if (resp.responseText.indexOf('You have reached your table quota') !== -1) {
|
||||
self.set('contentPane', 'datasetQuotaExceeded');
|
||||
} else {
|
||||
self.set('contentPane', 'addLayerFailed');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._uploadModel.bind('change', function () {
|
||||
this.trigger('change:upload', this);
|
||||
}, this);
|
||||
this._visualizationFetchModel.bind('change', this._fetchCollection, this);
|
||||
this.bind('change:listing', this._fetchCollection, this);
|
||||
|
||||
this._tablesCollection.bind('change:selected', function (changedModel, wasSelected) {
|
||||
this._selectedDatasetsCollection[wasSelected ? 'add' : 'remove'](changedModel);
|
||||
}, this);
|
||||
this._tablesCollection.bind('sync', function () {
|
||||
this._selectedDatasetsCollection.each(function (model) {
|
||||
var sameModel = this._tablesCollection.get(model.id);
|
||||
if (sameModel) {
|
||||
sameModel.set('selected', true);
|
||||
}
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
_fetchCollection: function () {
|
||||
var params = this._visualizationFetchModel.attributes;
|
||||
var types;
|
||||
|
||||
if (this._visualizationFetchModel.isSearching()) {
|
||||
// Supporting search in data library and user datasets at the same time
|
||||
types = 'table,remote';
|
||||
} else {
|
||||
types = params.library ? 'remote' : 'table';
|
||||
}
|
||||
|
||||
this._tablesCollection.fetch({
|
||||
data: {
|
||||
locked: '',
|
||||
q: params.q,
|
||||
page: params.page,
|
||||
tags: params.tag,
|
||||
shared: params.shared,
|
||||
only_liked: params.liked,
|
||||
type: '',
|
||||
types: types
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onCollectionChange: function () {
|
||||
this._selectedDatasetsCollection.reset(
|
||||
this._tablesCollection.where({ selected: true })
|
||||
);
|
||||
},
|
||||
|
||||
_addNewLayer: function (tableModel, empty) {
|
||||
this.set('contentPane', 'addingNewLayer');
|
||||
|
||||
this._userActions.createLayerFromTable(tableModel, {
|
||||
success: function (model) {
|
||||
this._userModel.updateTableCount();
|
||||
this.trigger('addLayerDone');
|
||||
MetricsTracker.track(MetricsTypes.CREATED_LAYER, {
|
||||
empty: !!empty,
|
||||
layer_id: model.get('id')
|
||||
});
|
||||
}.bind(this),
|
||||
error: function (req, resp) {
|
||||
if (resp.responseText.indexOf('You have reached your table quota') !== -1) {
|
||||
this.set('contentPane', 'datasetQuotaExceeded');
|
||||
} else {
|
||||
this.set('contentPane', 'addLayerFailed');
|
||||
}
|
||||
}.bind(this)
|
||||
});
|
||||
}
|
||||
});
|
||||
160
lib/assets/javascripts/builder/components/modals/add-layer/add-layer-view.js
Executable file
@@ -0,0 +1,160 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./add-layer.tpl');
|
||||
var FooterView = require('./footer/footer-view');
|
||||
var NavigationView = require('./content/navigation-view');
|
||||
var ListingView = require('./content/listing-view');
|
||||
var TabPaneView = require('builder/components/tab-pane/tab-pane-view');
|
||||
var TabPaneCollection = require('builder/components/tab-pane/tab-pane-collection');
|
||||
var ErrorDetailsView = require('builder/components/background-importer/error-details-view');
|
||||
var ViewFactory = require('builder/components/view-factory');
|
||||
var renderLoading = require('builder/components/loading/render-loading');
|
||||
var ErrorView = require('builder/components/error/error-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'modalModel',
|
||||
'createModel',
|
||||
'configModel',
|
||||
'userModel',
|
||||
'pollingModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Add layer dialog, typically used from editor
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Dialog-content Dialog-content--expanded',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initModels();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template());
|
||||
|
||||
this._initViews();
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this._guessingModel = new Backbone.Model({
|
||||
guessing: true
|
||||
});
|
||||
|
||||
this._privacyModel = new Backbone.Model({
|
||||
privacy: this._userModel.canCreatePrivateDatasets() ? 'PRIVATE' : 'PUBLIC'
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._createModel, 'addLayerDone', this._modalModel.destroy.bind(this._modalModel));
|
||||
this.listenTo(this._createModel, 'change:contentPane', this._onChangeContentView);
|
||||
this.listenTo(this._pollingModel, 'importByUploadData', this._modalModel.destroy.bind(this._modalModel));
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var self = this;
|
||||
|
||||
this._navigationView = new NavigationView({
|
||||
el: this.$('.js-navigation'),
|
||||
userModel: this._userModel,
|
||||
routerModel: this._createModel.getVisualizationFetchModel(),
|
||||
createModel: this._createModel,
|
||||
tablesCollection: this._createModel.getTablesCollection(),
|
||||
configModel: this._configModel
|
||||
});
|
||||
this._navigationView.render();
|
||||
this.addView(this._navigationView);
|
||||
|
||||
this._tabPaneCollection = new TabPaneCollection([
|
||||
{
|
||||
name: 'listing',
|
||||
selected: this._createModel.get('contentPane') === 'listing',
|
||||
createContentView: function () {
|
||||
return new ListingView({
|
||||
createModel: self._createModel,
|
||||
configModel: self._configModel,
|
||||
userModel: self._userModel,
|
||||
privacyModel: self._privacyModel,
|
||||
guessingModel: self._guessingModel
|
||||
});
|
||||
}
|
||||
}, {
|
||||
name: 'creatingFromScratch',
|
||||
selected: this._createModel.get('contentPane') === 'creatingFromScratch',
|
||||
createContentView: function () {
|
||||
return ViewFactory.createByHTML(
|
||||
renderLoading({
|
||||
title: _t('components.modals.add-layer.create-loading-title')
|
||||
})
|
||||
);
|
||||
}
|
||||
}, {
|
||||
name: 'addingNewLayer',
|
||||
selected: this._createModel.get('contentPane') === 'addingNewLayer',
|
||||
createContentView: function () {
|
||||
return ViewFactory.createByHTML(
|
||||
renderLoading({
|
||||
title: _t('components.modals.add-layer.adding-new-layer')
|
||||
})
|
||||
);
|
||||
}
|
||||
}, {
|
||||
name: 'addLayerFailed',
|
||||
selected: this._createModel.get('contentPane') === 'addLayerFailed',
|
||||
createContentView: function () {
|
||||
return new ErrorView({
|
||||
title: _t('components.modals.add-layer.add-layer-error')
|
||||
});
|
||||
}
|
||||
}, {
|
||||
name: 'datasetQuotaExceeded',
|
||||
selected: this._createModel.get('contentPane') === 'datasetQuotaExceeded',
|
||||
createContentView: function () {
|
||||
return new ErrorDetailsView({
|
||||
error: { errorCode: 8002 },
|
||||
userModel: self._userModel,
|
||||
configModel: self._configModel
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
var tabPaneView = new TabPaneView({
|
||||
collection: this._tabPaneCollection
|
||||
});
|
||||
this.addView(tabPaneView);
|
||||
this.$('.js-content-container').append(tabPaneView.render().el);
|
||||
|
||||
this._footerView = new FooterView({
|
||||
configModel: this._configModel,
|
||||
createModel: this._createModel,
|
||||
userModel: this._userModel,
|
||||
privacyModel: self._privacyModel,
|
||||
guessingModel: self._guessingModel
|
||||
|
||||
});
|
||||
this.addView(this._footerView);
|
||||
this.$('.js-footer').append(this._footerView.render().el);
|
||||
},
|
||||
|
||||
_onChangeContentView: function () {
|
||||
var context = this._createModel.get('contentPane');
|
||||
var paneModel = _.first(this._tabPaneCollection.where({ name: context }));
|
||||
var paneModelName = paneModel.get('name');
|
||||
paneModel.set('selected', true);
|
||||
|
||||
if (paneModelName === 'loading') {
|
||||
this._footerView.hide();
|
||||
}
|
||||
if (paneModelName !== 'listing') {
|
||||
this._navigationView.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
10
lib/assets/javascripts/builder/components/modals/add-layer/add-layer.tpl
Executable file
@@ -0,0 +1,10 @@
|
||||
<div class="Dialog-header Dialog-header--expanded CreateDialog-header with-separator">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--neutral">
|
||||
<i class="CDB-IconFont CDB-IconFont-add"></i>
|
||||
</div>
|
||||
<h2 class="CDB-Text CDB-Size-large u-mainTextColor u-bSpace"><%- _t('components.modals.add-layer.modal-title') %></h2>
|
||||
<h3 class="CDB-Text CDB-Size-medium u-altTextColor"><%- _t('components.modals.add-layer.modal-desc') %></h3>
|
||||
</div>
|
||||
<div class="Filters Filters--navListing Filters--static js-navigation"></div>
|
||||
<div class="js-content-container Dialog-body Dialog-body--expanded Dialog-body--create Dialog-body--noPaddingTop Dialog-body--withoutBorder"></div>
|
||||
<div class="Dialog-footer Dialog-footer--expanded CreateDialog-footer js-footer"></div>
|
||||
@@ -0,0 +1,57 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var randomQuote = require('builder/components/loading/random-quote');
|
||||
|
||||
/*
|
||||
* Content result default view
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-connect': '_onConnectClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.routerModel) throw new Error('routerModel is required');
|
||||
if (!opts.tablesCollection) throw new Error('tablesCollection is required');
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
if (!opts.template) throw new Error('template is required');
|
||||
|
||||
this._userModel = opts.userModel;
|
||||
this._routerModel = opts.routerModel;
|
||||
this._tablesCollection = opts.tablesCollection;
|
||||
this.template = opts.template;
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var type = this._routerModel.get('content_type');
|
||||
|
||||
this.$el.html(this.template({
|
||||
page: this._routerModel.get('page'),
|
||||
tag: this._routerModel.get('tag'),
|
||||
q: this._routerModel.get('q'),
|
||||
shared: this._routerModel.get('shared'),
|
||||
locked: this._routerModel.get('locked'),
|
||||
library: this._routerModel.get('library'),
|
||||
quote: randomQuote(),
|
||||
type: type,
|
||||
totalItems: this._tablesCollection.size(),
|
||||
totalEntries: this._tablesCollection.getTotalStat('total_entries')
|
||||
}));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._routerModel.bind('change', this.render, this);
|
||||
this._tablesCollection.bind('sync', this.render, this);
|
||||
this.add_related_model(this._routerModel);
|
||||
this.add_related_model(this._tablesCollection);
|
||||
},
|
||||
|
||||
_onConnectClick: function () {
|
||||
this.trigger('connectDataset', this);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
var cdb = require('internal-carto.js');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var moment = require('moment');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var template = require('./dataset-item.tpl');
|
||||
|
||||
/**
|
||||
* View representing an item in the list under datasets route.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
tagName: 'li',
|
||||
className: 'ModalBlockList-item ModalBlockList-item--full',
|
||||
|
||||
events: {
|
||||
'click .js-tag-link': '_onTagClick',
|
||||
'click': '_toggleSelected'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.createModel) throw new Error('createModel is required');
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
|
||||
this._createModel = opts.createModel;
|
||||
this._userModel = opts.userModel;
|
||||
this._routerModel = this._createModel.getVisualizationFetchModel();
|
||||
|
||||
this.model.on('change', this.render, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var tableModel = this.model.getTableModel();
|
||||
var permissionModel = this.model.getPermissionModel();
|
||||
var synchronizationModel = this.model.getSynchronizationModel();
|
||||
var tags = this.model.get('tags') || [];
|
||||
var description = cdb.core.sanitize.html(this.model.get('description'));
|
||||
var tableGeomColumnTypes = (tableModel.getGeometryType ? tableModel.getGeometryType() : tableModel.geomColumnTypes()) || [];
|
||||
|
||||
var d = {
|
||||
isRaster: this.model.isRaster(),
|
||||
geometryType: tableGeomColumnTypes.length > 0 ? tableGeomColumnTypes[0] : '',
|
||||
title: this.model.get('name'),
|
||||
isOwner: permissionModel.isOwner(this._userModel),
|
||||
owner: permissionModel.getOwner().renderData(this._userModel),
|
||||
showPermissionIndicator: !permissionModel.hasWriteAccess(this._userModel),
|
||||
description: description,
|
||||
privacy: this.model.get('privacy').toLowerCase(),
|
||||
timeDiff: moment(this.model.get('updated_at')).fromNow(),
|
||||
tags: tags,
|
||||
tagsCount: tags.length,
|
||||
maxTagsToShow: 3,
|
||||
rowCount: undefined,
|
||||
datasetSize: undefined,
|
||||
syncStatus: undefined,
|
||||
syncRanAt: undefined
|
||||
};
|
||||
|
||||
var rowCount = tableModel.get('row_count');
|
||||
if (rowCount >= 0) {
|
||||
d.rowCount = rowCount;
|
||||
d.rowCountFormatted = (rowCount < 10000 ? Utils.formatNumber(rowCount) : Utils.readizableNumber(rowCount));
|
||||
}
|
||||
|
||||
var datasetSize = tableModel.get('size');
|
||||
if (datasetSize >= 0) {
|
||||
d.datasetSize = Utils.readablizeBytes(datasetSize, true);
|
||||
}
|
||||
|
||||
if (!_.isEmpty(synchronizationModel)) {
|
||||
d.syncRanAt = moment(synchronizationModel.get('ran_at') || new Date()).fromNow();
|
||||
d.syncStatus = synchronizationModel.get('state');
|
||||
}
|
||||
|
||||
this.$el.html(template(d));
|
||||
|
||||
this._renderTooltips();
|
||||
|
||||
// Item selected?
|
||||
this.$el.toggleClass('is-selected', !!this.model.get('selected'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderTooltips: function () {
|
||||
if (!_.isEmpty(this.model.get('synchronization'))) {
|
||||
this.addView(
|
||||
new TipsyTooltipView({
|
||||
el: this.$('.DatasetsList-itemStatus'),
|
||||
title: function (e) {
|
||||
return $(this).attr('data-title');
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_onTagClick: function (ev) {
|
||||
var tag = $(ev.target).val();
|
||||
|
||||
if (tag) {
|
||||
this._routerModel.set('tag', tag);
|
||||
}
|
||||
},
|
||||
|
||||
_toggleSelected: function (ev) {
|
||||
// Let links use default behaviour
|
||||
if (ev.target.tagName !== 'A') {
|
||||
this.killEvent(ev);
|
||||
if (this._createModel.canSelect(this.model)) {
|
||||
this.model.set('selected', !this.model.get('selected'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
<div class="DatasetsList-itemCategory is--<%- isRaster ? 'raster' : geometryType %>Dataset">
|
||||
<% if (syncStatus) { %>
|
||||
<i
|
||||
<% if (syncStatus === "failure") { %>
|
||||
data-title="<%- _t('components.modals.add-layer.datasets.item.sync-failed') %> <%- syncRanAt %>"
|
||||
<% } else if (syncStatus === "syncing") { %>
|
||||
data-title="<%- _t('components.modals.add-layer.datasets.item.syncing') %>"
|
||||
<% } else { %>
|
||||
data-title="<%- _t('components.modals.add-layer.datasets.item.synced') %> <%- syncRanAt %>"
|
||||
<% } %>
|
||||
class="CDB-IconFont CDB-IconFont-wifi DatasetsList-itemStatus is-<%- syncStatus %>"></i>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="ModalDataset-itemInfo">
|
||||
<div class="ModalDataset-itemInfoTitle">
|
||||
<h3 class="CDB-Text CDB-Size-large u-bSpace u-ellipsis">
|
||||
<%- title %>
|
||||
<% if (showPermissionIndicator) { %>
|
||||
<span class="Tag Tag--outline Tag-outline--grey CDB-Text CDB-Size-small u-upperCase">
|
||||
<%- _t('components.modals.add-layer.datasets.item.read') %>
|
||||
</span>
|
||||
<% } %>
|
||||
</h3>
|
||||
<% if (description && description.length > 0) { %>
|
||||
<p class="u-ellipsis CDB-Text CDB-Size-medium u-altTextColor" title="<%- description %>"><%- description %></p>
|
||||
<% } else { %>
|
||||
<span class="NoResults CDB-Text CDB-Size-medium"><%- _t('components.modals.add-layer.datasets.item.no-description') %></span>
|
||||
<% } %>
|
||||
</div>
|
||||
<div>
|
||||
<div class="DatasetsList-itemMeta">
|
||||
|
||||
<span class="CDB-Tag is-<%- privacy %> CDB-Text is-semibold CDB-Size-small u-upperCase">
|
||||
<%- privacy %>
|
||||
</span>
|
||||
<% if (rowCount) { %>
|
||||
<span class="RowsIndicator">
|
||||
<span class="CDB-Text CDB-Size-small u-altTextColor"><%- rowCountFormatted %> <%- _t('components.modals.add-layer.datasets.item.rows-pluralize', { smart_count: rowCount }) %></span>
|
||||
</span>
|
||||
<% } %>
|
||||
<% if (datasetSize) { %>
|
||||
<span class="SizeIndicator">
|
||||
<span class="CDB-Text CDB-Size-small u-altTextColor"><%- datasetSize %></span>
|
||||
</span>
|
||||
<% } %>
|
||||
<span class="DatasetsList-itemTimeDiff DefaultTimeDiff">
|
||||
<span class="CDB-Text CDB-Size-small u-altTextColor"><%- timeDiff %></span>
|
||||
<% if (!isOwner) { %>
|
||||
<span class="CDB-Text CDB-Size-small u-altTextColor u-lSpace--xl u-rSpace">
|
||||
<%- _t('components.modals.add-layer.datasets.item.by') %>
|
||||
</span>
|
||||
<span class="DatasetsList-avatar">
|
||||
<img class="DatasetsList-avatarImg" src="<%- owner.avatar_url %>" alt="<%- owner.name || owner.username %>" title="<%- owner.name || owner.username %>" />
|
||||
</span>
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="DatasetsList-itemMeta DatasetsList-itemTags">
|
||||
<% if (tagsCount > 0) { %>
|
||||
<div class="DefaultTags CDB-Text CDB-Size-small">
|
||||
<% for (var i = 0, l = Math.min(maxTagsToShow, tags.length); i < l; ++i) { %>
|
||||
<button class="CDB-Text CDB-Size-small u-upperCase DefaultTags-item js-tag-link u-actionTextColor" value="<%- tags[i] %>"><%- tags[i] %></button><% if (i !== (l-1)) { %><% } %>
|
||||
<% } %>
|
||||
<% if (tagsCount > maxTagsToShow) { %>
|
||||
<%- _t('components.modals.add-layer.datasets.item.tags-more', { tagsCount: tagsCount - maxTagsToShow }) %>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<span class="NoResults CDB-Text CDB-Size-small u-altTextColor"><%- _t('components.modals.add-layer.datasets.item.no-tags') %></span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="IntermediateInfo">
|
||||
<div class="LayoutIcon LayoutIcon--negative">
|
||||
<i class="CDB-IconFont CDB-IconFont-cockroach"></i>
|
||||
</div>
|
||||
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<%- _t('components.modals.add-layer.datasets.error.title') %>
|
||||
</h4>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor"><%- _t('components.modals.add-layer.datasets.error.desc') %> <a class="js-mail-link" href="mailto:support@carto.com">support@carto.com</a>.</p>
|
||||
</div>
|
||||
@@ -0,0 +1,58 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var DATASETS_ITEMS = {
|
||||
'datasets': require('./dataset-item-view'),
|
||||
'remotes': require('./remote-dataset-item-view')
|
||||
};
|
||||
|
||||
/**
|
||||
* View representing the list of datasets
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'ul',
|
||||
className: 'DatasetsList fs-DatasetsList',
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.createModel) throw new Error('createModel is required');
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
|
||||
this._createModel = opts.createModel;
|
||||
this._userModel = opts.userModel;
|
||||
this._tablesCollection = this._createModel.getTablesCollection();
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this._tablesCollection.each(this._addItem, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._tablesCollection.bind('sync', this.render, this);
|
||||
this.add_related_model(this._tablesCollection);
|
||||
},
|
||||
|
||||
_addItem: function (m, i) {
|
||||
var type = m.get('type') === 'remote' ? 'remotes' : 'datasets';
|
||||
|
||||
var item = new DATASETS_ITEMS[type]({
|
||||
model: m,
|
||||
createModel: this._createModel,
|
||||
userModel: this._userModel
|
||||
});
|
||||
|
||||
this.addView(item);
|
||||
this.$el.append(item.render().el);
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.$el.removeClass('is-hidden');
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.$el.addClass('is-hidden');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="IntermediateInfo">
|
||||
<div class="CDB-LoaderIcon CDB-LoaderIcon--big is-dark js-loader">
|
||||
<svg class="CDB-LoaderIcon-spinner" viewbox="0 0 50 50">
|
||||
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-bSpace u-secondaryTextColor u-tSpace-xl">
|
||||
<%- _t('components.modals.add-layer.datasets.' + (q || tag ? 'searching' : 'loading')) %>...
|
||||
</h4>
|
||||
<div class="CDB-Text CDB-Size-medium u-altTextColor"><%= quote %></div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="IntermediateInfo">
|
||||
<div class="LayoutIcon <% (q || tag) ? 'LayoutIcon--negative' : '' %>">
|
||||
<i class="CDB-IconFont
|
||||
<% if (shared) { %> CDB-IconFont-defaultUser
|
||||
<% } else if (locked) { %> CDB-IconFont-lock
|
||||
<% } else if (library) { %> CDB-IconFont-dribbble
|
||||
<% } else { %> CDB-IconFont-lens <% } %>" />
|
||||
</div>
|
||||
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-bSpace u-secondaryTextColor u-tSpace-xl">
|
||||
<% if (page > 1 || totalItems === 0 && totalEntries > 0) { %>
|
||||
<%- _t('components.modals.add-layer.datasets.no-results.desc') %>
|
||||
<% } %>
|
||||
|
||||
<% if (( tag || q ) && totalItems === 0 && totalEntries === 0) { %>
|
||||
0 <%- tag || q %> <%- type %> <%- _t('components.modals.add-layer.datasets.no-results.found') %>
|
||||
<% } %>
|
||||
|
||||
<% if (page === 1 && !tag && !q && totalItems === 0 && totalEntries === 0) { %>
|
||||
<%- _t('components.modals.add-layer.datasets.no-results.there-are-no') %> <%- shared === "only" ? 'shared' : '' %> <%- locked ? 'locked' : '' %> <%- library ? 'library' : '' %> <%- type %>
|
||||
<% } %>
|
||||
</h4>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<% if (!tag && !q && totalItems === 0 && totalEntries === 0) { %>
|
||||
<%- _t('components.modals.add-layer.datasets.no-results.no-fun', { type: type }) %>
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,72 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var PaginationModel = require('builder/components/pagination/pagination-model');
|
||||
var PaginationView = require('builder/components/pagination/pagination-view');
|
||||
|
||||
/**
|
||||
* Responsible for the datasets paginator
|
||||
* ___________________________________________________________________________
|
||||
* | |
|
||||
* | Page 2 of 42 [1] 2 [3][4][5] |
|
||||
* |___________________________________________________________________________|
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'DatasetsPaginator',
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.routerModel) throw new TypeError('routerModel is required');
|
||||
if (!opts.tablesCollection) throw new TypeError('tablesCollection is required');
|
||||
|
||||
this._routerModel = opts.routerModel;
|
||||
this._tablesCollection = opts.tablesCollection;
|
||||
|
||||
this.model = new PaginationModel({
|
||||
current_page: this._routerModel.get('page')
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
this._initViews();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html('');
|
||||
if (this.model.shouldBeVisible()) {
|
||||
this.$el.append(this.paginationView.render().el);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this.render, this);
|
||||
this.model.bind('change:current_page', function () {
|
||||
this._routerModel.set('page', this.model.get('current_page'));
|
||||
}, this);
|
||||
this._tablesCollection.bind('sync', this._updatePaginationModelByCollection, this);
|
||||
this._routerModel.bind('change:page', this._updatePaginationModelByRouterModel, this);
|
||||
|
||||
this.add_related_model(this._routerModel);
|
||||
this.add_related_model(this._tablesCollection);
|
||||
this.add_related_model(this.model);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this.paginationView = new PaginationView({
|
||||
model: this.model
|
||||
});
|
||||
this.addView(this.paginationView);
|
||||
},
|
||||
|
||||
_updatePaginationModelByCollection: function () {
|
||||
this.model.set({
|
||||
per_page: this._tablesCollection.getDefaultParam('per_page'),
|
||||
total_count: this._tablesCollection.getTotalStat('total_entries')
|
||||
});
|
||||
},
|
||||
|
||||
_updatePaginationModelByRouterModel: function () {
|
||||
this.model.set('current_page', this._routerModel.get('page'));
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var ContentResultView = require('./content-result-view');
|
||||
var DatasetsListView = require('./datasets-list-view');
|
||||
var DatasetsPaginationView = require('./datasets-pagination-view');
|
||||
var noResultsTemplate = require('./no-datasets.tpl');
|
||||
var datasetsNoResultTemplate = require('./datasets-no-result.tpl');
|
||||
var datasetsErrorTemplate = require('./datasets-error.tpl');
|
||||
var datasetsLoaderTemplate = require('./datasets-loader.tpl');
|
||||
|
||||
/**
|
||||
* Datasets list view
|
||||
*
|
||||
* Show datasets view to select them for
|
||||
* creating a map or importing a dataset
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
if (!opts.createModel) throw new Error('createModel is required');
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
|
||||
this._createModel = opts.createModel;
|
||||
this._userModel = opts.userModel;
|
||||
this._routerModel = this._createModel.getVisualizationFetchModel();
|
||||
this._tablesCollection = this._createModel.getTablesCollection();
|
||||
|
||||
this._initViews();
|
||||
this._initBinds();
|
||||
this._onDataLoading();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._routerModel.bind('change', this._onRouterChange, this);
|
||||
this._tablesCollection.bind('loading', this._onDataLoading, this);
|
||||
this._tablesCollection.bind('sync', this._onDataFetched, this);
|
||||
this._tablesCollection.bind('error', function (e) {
|
||||
// Old requests can be stopped, so aborted requests are not
|
||||
// considered as an error
|
||||
if (!e || (e && e.statusText !== 'abort')) {
|
||||
this._onDataError();
|
||||
}
|
||||
}, this);
|
||||
this.add_related_model(this._routerModel);
|
||||
this.add_related_model(this._tablesCollection);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this.controlledViews = {}; // All available views
|
||||
this.enabledViews = []; // Visible views
|
||||
|
||||
var noDatasetsView = new ContentResultView({
|
||||
className: 'ContentResult no-datasets',
|
||||
userModel: this._userModel,
|
||||
routerModel: this._routerModel,
|
||||
tablesCollection: this._tablesCollection,
|
||||
template: noResultsTemplate
|
||||
});
|
||||
noDatasetsView.bind('connectDataset', function () {
|
||||
if (this._userModel.canCreateDatasets()) {
|
||||
this._createModel.set('listing', 'import');
|
||||
}
|
||||
}, this);
|
||||
noDatasetsView.render().hide();
|
||||
this.controlledViews['no_datasets'] = noDatasetsView;
|
||||
this.$el.append(noDatasetsView.el);
|
||||
this.addView(noDatasetsView);
|
||||
|
||||
var listView = new DatasetsListView({
|
||||
userModel: this._userModel,
|
||||
createModel: this._createModel
|
||||
});
|
||||
this.controlledViews.list = listView;
|
||||
this.$el.append(listView.render().el);
|
||||
this.addView(listView);
|
||||
|
||||
var noResultsView = new ContentResultView({
|
||||
userModel: this._userModel,
|
||||
routerModel: this._routerModel,
|
||||
tablesCollection: this._tablesCollection,
|
||||
template: datasetsNoResultTemplate
|
||||
});
|
||||
noResultsView.render().hide();
|
||||
this.controlledViews.no_results = noResultsView;
|
||||
this.$el.append(noResultsView.el);
|
||||
this.addView(noResultsView);
|
||||
|
||||
var errorView = new ContentResultView({
|
||||
userModel: this._userModel,
|
||||
routerModel: this._routerModel,
|
||||
tablesCollection: this._tablesCollection,
|
||||
template: datasetsErrorTemplate
|
||||
});
|
||||
errorView.render().hide();
|
||||
this.controlledViews.error = errorView;
|
||||
this.$el.append(errorView.el);
|
||||
this.addView(errorView);
|
||||
|
||||
var mainLoaderView = new ContentResultView({
|
||||
userModel: this._userModel,
|
||||
routerModel: this._routerModel,
|
||||
tablesCollection: this._tablesCollection,
|
||||
template: datasetsLoaderTemplate
|
||||
});
|
||||
this.controlledViews.main_loader = mainLoaderView;
|
||||
this.$el.append(mainLoaderView.render().el);
|
||||
this.addView(mainLoaderView);
|
||||
|
||||
var datasetsPaginationView = new DatasetsPaginationView({
|
||||
routerModel: this._routerModel,
|
||||
tablesCollection: this._tablesCollection
|
||||
});
|
||||
this.controlledViews.content_footer = datasetsPaginationView;
|
||||
this.$el.append(datasetsPaginationView.render().el);
|
||||
this.addView(datasetsPaginationView);
|
||||
},
|
||||
|
||||
_onRouterChange: function () {
|
||||
this._hideBlocks();
|
||||
this._showBlocks([ 'main_loader' ]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Arguments may vary, depending on if it's the collection or a model that triggers the event callback.
|
||||
* @private
|
||||
*/
|
||||
_onDataFetched: function () {
|
||||
var activeViews = [ 'content_footer' ];
|
||||
var tag = this._routerModel.get('tag');
|
||||
var q = this._routerModel.get('q');
|
||||
var shared = this._routerModel.get('shared');
|
||||
var locked = this._routerModel.get('locked');
|
||||
var library = this._routerModel.get('library');
|
||||
|
||||
if (library && this._tablesCollection.getTotalStat('total_user_entries') === 0) {
|
||||
activeViews.push('no_datasets');
|
||||
}
|
||||
|
||||
if (this._tablesCollection.size() === 0) {
|
||||
if (!tag && !q && shared === 'no' && !locked) {
|
||||
if (!library) {
|
||||
this._goToLibrary();
|
||||
return;
|
||||
} else {
|
||||
activeViews.push('no_results');
|
||||
}
|
||||
} else {
|
||||
activeViews.push('no_results');
|
||||
}
|
||||
} else {
|
||||
activeViews.push('list');
|
||||
}
|
||||
|
||||
this._hideBlocks();
|
||||
this._showBlocks(activeViews);
|
||||
},
|
||||
|
||||
_onDataLoading: function () {
|
||||
this._hideBlocks();
|
||||
this._showBlocks([ 'main_loader' ]);
|
||||
},
|
||||
|
||||
_onDataError: function (e) {
|
||||
this._hideBlocks();
|
||||
this._showBlocks([ 'error' ]);
|
||||
},
|
||||
|
||||
_showBlocks: function (views) {
|
||||
var self = this;
|
||||
if (views) {
|
||||
_.each(views, function (v) {
|
||||
if (self.controlledViews[v]) {
|
||||
self.controlledViews[v].show();
|
||||
self.enabledViews.push(v);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.enabledViews = [];
|
||||
_.each(this.controlledViews, function (v) {
|
||||
v.show();
|
||||
self.enabledViews.push(v);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_goToLibrary: function () {
|
||||
this._routerModel.set({
|
||||
shared: 'no',
|
||||
library: true,
|
||||
page: 1
|
||||
});
|
||||
},
|
||||
|
||||
_hideBlocks: function (views) {
|
||||
var self = this;
|
||||
if (views) {
|
||||
_.each(views, function (v) {
|
||||
if (self.controlledViews[v]) {
|
||||
self.controlledViews[v].hide();
|
||||
self.enabledViews = _.without(self.enabledViews, v);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_.each(this.controlledViews, function (v) {
|
||||
v.hide();
|
||||
});
|
||||
self.enabledViews = [];
|
||||
}
|
||||
},
|
||||
|
||||
_isBlockEnabled: function (name) {
|
||||
if (name) {
|
||||
return _.contains(this.enabledViews, name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl">
|
||||
<%- _t('components.modals.add-layer.datasets.no-datasets.title') %>
|
||||
</h4>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<% var connectDatasetHTML = '<button class="Button--link js-connect">' + _t('components.modals.add-layer.datasets.no-datasets.connect-datasets') + '</button>'; %>
|
||||
<% var searchHTML = '<strong>' + _t('components.modals.add-layer.datasets.no-datasets.search') + '</strong>'; %>
|
||||
<%= _t('components.modals.add-layer.datasets.no-datasets.desc', {
|
||||
connectDataset: connectDatasetHTML,
|
||||
search: searchHTML
|
||||
}) %>
|
||||
</p>
|
||||
<div class="NoDatasets-illustration"></div>
|
||||
@@ -0,0 +1,123 @@
|
||||
var cdb = require('internal-carto.js');
|
||||
var $ = require('jquery');
|
||||
var DatasetItem = require('./dataset-item-view');
|
||||
var moment = require('moment');
|
||||
var markdown = require('markdown');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var UploadConfig = require('builder/config/upload-config');
|
||||
var template = require('./remote-dataset-item.tpl');
|
||||
|
||||
/**
|
||||
* Remote dataset item view
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = DatasetItem.extend({
|
||||
tagName: 'li',
|
||||
className: 'ModalBlockList-item ModalBlockList-item--full',
|
||||
|
||||
events: {
|
||||
'click .js-tag-link': '_onTagClick',
|
||||
'click': '_toggleSelected'
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var tableModel = this.model.getTableModel();
|
||||
var tags = this.model.get('tags') || [];
|
||||
var description = cdb.core.sanitize.html(this.model.get('description') || '');
|
||||
var source = markdown.toHTML(this.model.get('source') || '');
|
||||
var tableGeomColumnTypes = tableModel.getGeometryType() || [];
|
||||
|
||||
var d = {
|
||||
isRaster: this.model.isRaster(),
|
||||
geometryType: tableGeomColumnTypes.length > 0 ? tableGeomColumnTypes[0] : '',
|
||||
title: this.model.get('display_name') || this.model.get('name'),
|
||||
source: source,
|
||||
description: description,
|
||||
timeDiff: moment(this.model.get('updated_at')).fromNow(),
|
||||
tags: tags,
|
||||
tagsCount: tags.length,
|
||||
routerModel: this._routerModel,
|
||||
maxTagsToShow: 3,
|
||||
canImportDataset: this._canImportDataset(),
|
||||
rowCount: undefined,
|
||||
datasetSize: undefined
|
||||
};
|
||||
|
||||
var rowCount = tableModel.get('row_count');
|
||||
if (rowCount >= 0) {
|
||||
d.rowCount = rowCount;
|
||||
d.rowCountFormatted = (rowCount < 10000 ? Utils.formatNumber(rowCount) : Utils.readizableNumber(rowCount));
|
||||
}
|
||||
|
||||
var datasetSize = tableModel.get('size');
|
||||
if (datasetSize >= 0) {
|
||||
d.datasetSize = Utils.readablizeBytes(
|
||||
datasetSize,
|
||||
datasetSize.toString().length > 9
|
||||
);
|
||||
}
|
||||
|
||||
this.$el.html(template(d));
|
||||
this._setItemClasses();
|
||||
this._renderTooltips();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_setItemClasses: function () {
|
||||
// Item selected?
|
||||
this.$el.toggleClass('is-selected', !!this.model.get('selected'));
|
||||
// Check if it is selectable
|
||||
this.$el.toggleClass('is-selectable', !!this._canImportDataset());
|
||||
// Check if it is importable
|
||||
this.$el.toggleClass('is-banned', !this._canImportDataset());
|
||||
},
|
||||
|
||||
_renderTooltips: function () {
|
||||
this.addView(
|
||||
new TipsyTooltipView({
|
||||
el: this.$('.DatasetsList-itemStatus'),
|
||||
title: function (e) {
|
||||
return $(this).attr('data-title');
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_onTagClick: function (ev) {
|
||||
if (ev) {
|
||||
this.killEvent(ev);
|
||||
}
|
||||
|
||||
var tag = $(ev.target).val();
|
||||
|
||||
if (tag) {
|
||||
this._routerModel.set({
|
||||
tag: tag,
|
||||
library: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_canImportDataset: function () {
|
||||
var tableModel = this.model.getTableModel();
|
||||
var tableSize = tableModel.get('size') || 0;
|
||||
return (
|
||||
this._userModel.get('remaining_byte_quota') * UploadConfig.fileTimesBigger >= tableSize &&
|
||||
this._userModel.get('limits')['import_file_size'] > tableSize
|
||||
);
|
||||
},
|
||||
|
||||
_toggleSelected: function (ev) {
|
||||
// Let links use default behaviour
|
||||
if (ev.target.tagName !== 'A') {
|
||||
this.killEvent(ev);
|
||||
if (this._canImportDataset() && this._createModel.canSelect(this.model)) {
|
||||
this.model.set('selected', !this.model.get('selected'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
<div class="DatasetsList-itemCategory is--<%- isRaster ? 'raster' : geometryType %>Dataset">
|
||||
<i data-title="Public dataset" class="CDB-IconFont CDB-IconFont-book DatasetsList-itemStatus <%- canImportDataset ? 'is-public' : 'is-banned' %>"></i>
|
||||
</div>
|
||||
<div class="ModalDataset-itemInfo">
|
||||
<div class="ModalDataset-itemInfoTitle">
|
||||
<h3 class="CDB-Text CDB-Size-large u-bSpace u-ellipsis"><%- title %></h3>
|
||||
<% if (description && description.length > 0) { %>
|
||||
<p class="u-ellipsis CDB-Text CDB-Size-medium u-altTextColor" title="<%- description %>"><%- description %></p>
|
||||
<% } else { %>
|
||||
<span class="NoResults CDB-Text CDB-Size-medium"><%- _t('components.modals.add-layer.datasets.item.no-description')%></span>
|
||||
<% } %>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="DatasetsList-itemMeta CDB-Text CDB-Size-small u-altTextColor">
|
||||
<% if (rowCount) { %>
|
||||
<li class="RowsIndicator">
|
||||
<%- rowCountFormatted %> <%- _t('components.modals.add-layer.datasets.item.rows-pluralize', { smart_count: rowCount }) %>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if (datasetSize) { %>
|
||||
<li class="SizeIndicator">
|
||||
<%- datasetSize %>
|
||||
</li>
|
||||
<% } %>
|
||||
<li class="DatasetsList-itemTimeDiff DefaultTimeDiff CDB-Text CDB-Size-small u-altTextColor">
|
||||
<%- timeDiff %> <span class="DatasetsList-itemSource js-source"><% if (source) { %><%- _t('components.modals.add-layer.datasets.item.from') %> <%= cdb.core.sanitize.html(source) %><% } %></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="DatasetsList-itemMeta DatasetsList-itemTags">
|
||||
<% if (tagsCount > 0) { %>
|
||||
<div class="DefaultTags CDB-Text CDB-Size-small">
|
||||
<% for (var i = 0, l = Math.min(maxTagsToShow, tags.length); i < l; ++i) { %>
|
||||
<button class="CDB-Text CDB-Size-small u-upperCase DefaultTags-item js-tag-link u-actionTextColor" value="<%- tags[i] %>"><%- tags[i] %></button><% if (i !== (l-1)) { %><% } %>
|
||||
<% } %>
|
||||
<% if (tagsCount > maxTagsToShow) { %>
|
||||
<%- _t('components.modals.add-layer.datasets.item.tags-more', { tagsCount: tagsCount - maxTagsToShow }) %>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<span class="NoResults CDB-Text CDB-Size-small u-altTextColor"><%- _t('components.modals.add-layer.datasets.item.no-tags') %></span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="ImportPanel-header">
|
||||
<div class="LayoutIcon u-bSpace--xl">
|
||||
<i class="CDB-IconFont CDB-IconFont-gift"></i>
|
||||
</div>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">ArcGIS<sup>™</sup> <%- _t('components.modals.add-layer.imports.connector') %></h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-bSpace--xl">
|
||||
<%= _t('components.modals.add-layer.imports.arcgis.fallback-desc', {
|
||||
brand: 'ArcGIS<sup>™</sup>'
|
||||
}) %>
|
||||
</p>
|
||||
<a href="mailto:sales@carto.com?subject=<%- _t('components.modals.add-layer.imports.demo-email-title', { name: 'ArcGIS' }) %>&body=<%- _t('components.modals.add-layer.imports.demo-email-desc', { name: 'ArcGIS' }) %>" class="CDB-Button CDB-Button--primary CDB-Button--medium">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-layer.imports.ask-for-demo') %></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="ImportPanel-header">
|
||||
<div class="LayoutIcon u-bSpace--xl">
|
||||
<i class="CDB-IconFont CDB-IconFont-gift"></i>
|
||||
</div>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">Box <%- _t('components.modals.add-layer.imports.connector') %></h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-bSpace--xl">
|
||||
<%- _t('components.modals.add-layer.imports.box.fallback-desc', { brand: 'Box' }) %>
|
||||
</p>
|
||||
<a href="mailto:sales@carto.com?subject=<%- _t('components.modals.add-layer.imports.demo-email-title', { name: 'Box' }) %>&body=<%- _t('components.modals.add-layer.imports.demo-email-desc', { name: 'Box' }) %>" class="CDB-Button CDB-Button--primary CDB-Button--medium">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-layer.imports.ask-for-demo') %></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="ImportPanel-header">
|
||||
<div class="LayoutIcon u-bSpace--xl">
|
||||
<i class="CDB-IconFont CDB-IconFont-gift"></i>
|
||||
</div>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">Instagram <%- _t('components.modals.add-layer.imports.connector') %></h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-bSpace--xl"><%- _t('components.modals.add-layer.imports.instagram.fallback-desc', { brand: 'Instagram' }) %></p>
|
||||
<a href="mailto:sales@carto.com?subject=<%- _t('components.modals.add-layer.imports.demo-email-title', { name: 'Instagram' }) %>&body=<%- _t('components.modals.add-layer.imports.demo-email-desc', { name: 'Instagram' }) %>" class="CDB-Button CDB-Button--primary CDB-Button--medium">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-layer.imports.ask-for-demo') %></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="ImportPanel-header">
|
||||
<div class="LayoutIcon u-bSpace--xl">
|
||||
<i class="CDB-IconFont CDB-IconFont-gift"></i>
|
||||
</div>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">MailChimp <%- _t('components.modals.add-layer.imports.connector') %></h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-bSpace--xl"><%- _t('components.modals.add-layer.imports.mailchimp.fallback-desc', { brand: 'Mailchimp' }) %></p>
|
||||
<a href="mailto:sales@carto.com?subject=<%- _t('components.modals.add-layer.imports.demo-email-title', { name: 'MailChimp' }) %>&body=<%- _t('components.modals.add-layer.imports.demo-email-desc', { name: 'MailChimp' }) %>" class="CDB-Button CDB-Button--primary CDB-Button--medium">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-layer.imports.ask-for-demo') %></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="ImportPanel-header">
|
||||
<div class="LayoutIcon u-bSpace--xl">
|
||||
<i class="CDB-IconFont CDB-IconFont-gift"></i>
|
||||
</div>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">Salesforce <%- _t('components.modals.add-layer.imports.connector') %></h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-bSpace--xl">
|
||||
<%- _t('components.modals.add-layer.imports.salesforce.fallback-desc', { brand: 'Salesforce' }) %>
|
||||
</p>
|
||||
<a href="mailto:sales@carto.com?subject=<%- _t('components.modals.add-layer.imports.demo-email-title', { name: 'Salesforce' }) %>&body=<%- _t('components.modals.add-layer.imports.demo-email-desc', { name: 'Salesforce' }) %>" class="CDB-Button CDB-Button--primary CDB-Button--medium">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-layer.imports.contact-us') %></span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="ImportPanel-header">
|
||||
<div class="LayoutIcon u-bSpace--xl">
|
||||
<i class="CDB-IconFont CDB-IconFont-gift"></i>
|
||||
</div>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m"><%- _t('components.modals.add-layer.imports.twitter.fallback-title', { brand: 'Twitter' }) %></h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-bSpace--xl">
|
||||
<%- _t('components.modals.add-layer.imports.twitter.fallback-desc', { brand: 'Twitter' }) %>
|
||||
</p>
|
||||
<a href="mailto:sales@carto.com?subject=<%- _t('components.modals.add-layer.imports.demo-email-title', { name: 'Twitter' }) %>&body=<%- _t('components.modals.add-layer.imports.demo-email-desc', { name: 'Twitter' }) %>" class="CDB-Button CDB-Button--primary CDB-Button--medium">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">ask for a demo</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<form class="Form js-form">
|
||||
<div class="Form-row">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium"><%- _t('components.modals.add-layer.imports.form-import.title') %></label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<input type="text" class="CDB-Text CDB-Size-medium Form-input Form-input--longer has-submit js-textInput" value="" placeholder="<%= _t('components.modals.add-layer.imports.arcgis.input-placeholder', { brand: 'ArcGIS Server™' }) %>" />
|
||||
<button type="submit" class="CDB-Text CDB-Size-small Form-inputSubmit u-upperCase u-actionTextColor Form-inputSubmit">
|
||||
<span><%- _t('components.modals.add-layer.imports.form-import.submit') %></span>
|
||||
</button>
|
||||
<div class="Form-inputError CDB-Text"><%- _t('components.modals.add-layer.imports.form-import.error-desc') %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Form-row">
|
||||
<div class="Form-rowLabel"></div>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<p class="CDB-Text CDB-Size-small Form-rowInfoText--centered Form-rowInfoText--block u-altTextColor">
|
||||
<%- _t('components.modals.add-layer.imports.form-import.format') %>: http://<host>/arcgis/rest/services/<folder>/<serviceName>/<serviceType><br/>
|
||||
<%- _t('components.modals.add-layer.imports.arcgis.url-desc') %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,19 @@
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<% if (state === 'selected') { %>
|
||||
<%= _t('components.modals.add-layer.imports.header-import.type-selected', { brand: 'ArcGIS<sup>™</sup>' }) %>
|
||||
<% } else { %>
|
||||
<%= _t('components.modals.add-layer.imports.header-import.type-import', { brand: 'ArcGIS<sup>™</sup>' }) %>
|
||||
<% } %>
|
||||
</h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<% if (state !== "selected") { %>
|
||||
<%= _t('components.modals.add-layer.imports.arcgis.import-data', { brand: 'ArcGIS<sup>™</sup>' }) %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.arcgis.sync-options') %>
|
||||
<% } %>
|
||||
</p>
|
||||
<% if (state === "selected") { %>
|
||||
<button class="NavButton NavButton--back ImportPanel-headerButton js-back">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev"></i>
|
||||
</button>
|
||||
<% } %>
|
||||
@@ -0,0 +1,48 @@
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var SelectedDatasetView = require('builder/components/modals/add-layer/content/imports/import-selected-dataset-view');
|
||||
var template = require('builder/components/modals/add-layer/content/imports/import-selected-dataset.tpl');
|
||||
|
||||
/**
|
||||
* Selected ArcGIS dataset
|
||||
*
|
||||
* - Displays the result when an ArcGIS url/dataset is selected, no matter the type.
|
||||
* - It will show available sync options if user can and the url is an ArcGIS layer.
|
||||
* - Upgrade link for people who don't have sync permissions.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = SelectedDatasetView.extend({
|
||||
render: function () {
|
||||
var title = this.options.fileAttrs.title && this.model.get('value')[this.options.fileAttrs.title] || this.model.get('value');
|
||||
var description = this._genDescription();
|
||||
var ext = this.options.fileAttrs.ext ? Utils.getFileExtension(title) : '';
|
||||
|
||||
if (this.options.fileAttrs.ext) {
|
||||
title = title && title.replace('.' + ext, '');
|
||||
}
|
||||
|
||||
var upgradeUrl = window.upgrade_url;
|
||||
var userCanSync = this._userModel.isActionEnabled('sync_tables');
|
||||
var customInstall = this._configModel.get('cartodb_com_hosted');
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
title: title,
|
||||
description: description,
|
||||
ext: ext,
|
||||
interval: this.model.get('interval'),
|
||||
importCanSync: this.options.acceptSync && this._isArcGISLayer(title),
|
||||
userCanSync: userCanSync,
|
||||
showTrial: this._userModel.canStartTrial(),
|
||||
showUpgrade: !userCanSync && !customInstall && upgradeUrl && !this._userModel.isInsideOrg(),
|
||||
upgradeUrl: upgradeUrl
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_isArcGISLayer: function (url) {
|
||||
return url.search(/([0-9]+\/|[0-9]+)/) !== -1;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
var FormView = require('builder/components/modals/add-layer/content/imports/import-data/import-data-form-view');
|
||||
var HeaderView = require('builder/components/modals/add-layer/content/imports/import-data/import-data-header-view');
|
||||
var SelectedDatasetView = require('./import-arcgis-selected-dataset-view');
|
||||
var ImportDataView = require('builder/components/modals/add-layer/content/imports/import-data/import-data-view');
|
||||
var headerTemplate = require('./import-arcgis-header.tpl');
|
||||
var formTemplate = require('./import-arcgis-form.tpl');
|
||||
|
||||
/**
|
||||
* Import ArcGIS panel
|
||||
*
|
||||
* - It only accepts an url, and it could be a map or a layer.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = ImportDataView.extend({
|
||||
options: {
|
||||
fileExtensions: [],
|
||||
type: 'service',
|
||||
service: 'arcgis',
|
||||
acceptSync: true,
|
||||
fileEnabled: false,
|
||||
fileAttrs: {
|
||||
ext: false,
|
||||
title: '',
|
||||
description: ''
|
||||
}
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var headerView = new HeaderView({
|
||||
el: this.$('.ImportPanel-header'),
|
||||
model: this.model,
|
||||
userModel: this._userModel,
|
||||
collection: this.collection,
|
||||
fileEnabled: this.options.fileEnabled,
|
||||
acceptSync: this.options.acceptSync,
|
||||
template: headerTemplate
|
||||
});
|
||||
headerView.render();
|
||||
this.addView(headerView);
|
||||
|
||||
var selected = new SelectedDatasetView({
|
||||
el: this.$('.DatasetSelected'),
|
||||
userModel: this._userModel,
|
||||
model: this.model,
|
||||
acceptSync: this.options.acceptSync,
|
||||
fileAttrs: this.options.fileAttrs,
|
||||
configModel: this._configModel
|
||||
});
|
||||
selected.render();
|
||||
this.addView(selected);
|
||||
|
||||
var formView = new FormView({
|
||||
el: this.$('.ImportPanel-form'),
|
||||
userModel: this._userModel,
|
||||
model: this.model,
|
||||
template: formTemplate,
|
||||
fileEnabled: this.options.fileEnabled
|
||||
});
|
||||
|
||||
formView.render();
|
||||
this.addView(formView);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,223 @@
|
||||
require('dragster');
|
||||
var Dropzone = require('dropzone');
|
||||
var $ = require('jquery');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./import-data-form.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
/**
|
||||
* Form view for url import for example
|
||||
*
|
||||
* - It accepts an url
|
||||
* - It checks if it is valid
|
||||
* - It could have a file option
|
||||
*
|
||||
*/
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
options: {
|
||||
template: '',
|
||||
fileEnabled: false
|
||||
},
|
||||
|
||||
events: {
|
||||
'keyup .js-textInput': '_onTextChanged',
|
||||
'submit .js-form': '_onSubmitForm'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this.template = opts.template || template;
|
||||
|
||||
this._initBinds();
|
||||
this._checkVisibility();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
this.template(this.options)
|
||||
);
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:state', this._checkVisibility, this);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
if (this.options.fileEnabled) {
|
||||
var self = this;
|
||||
this.$('.js-fileInput').bind('change', function (e) {
|
||||
if (this.files && this.files.length > 0) {
|
||||
self._onFileChanged(this.files);
|
||||
}
|
||||
this.value = '';
|
||||
});
|
||||
|
||||
this._initDropzone();
|
||||
}
|
||||
},
|
||||
|
||||
_initDropzone: function () {
|
||||
var el = $('html')[0]; // :(
|
||||
var self = this;
|
||||
|
||||
this.dragster = new Dragster(el); // eslint-disable-line
|
||||
|
||||
$(el).bind('dragster:enter', function (e) {
|
||||
self._showDropzone();
|
||||
});
|
||||
|
||||
$(el).bind('dragster:leave', function (e) {
|
||||
self._hideDropzone();
|
||||
});
|
||||
|
||||
if (el.dropzone) { // avoid loading the dropzone twice
|
||||
el.dropzone.destroy();
|
||||
}
|
||||
|
||||
this.dropzone = new Dropzone(el, {
|
||||
url: ':)',
|
||||
autoProcessQueue: false,
|
||||
previewsContainer: false
|
||||
});
|
||||
|
||||
this.dropzone.on('dragover', function () {
|
||||
self._showDropzone();
|
||||
});
|
||||
|
||||
this.dropzone.on('drop', function (ev) {
|
||||
var files = ev.dataTransfer.files;
|
||||
self._onFileChanged(files);
|
||||
self._hideDropzone();
|
||||
});
|
||||
},
|
||||
|
||||
_destroyDropzone: function () {
|
||||
var el = $('html')[0]; // :(
|
||||
|
||||
if (this.dragster) {
|
||||
this.dragster.removeListeners();
|
||||
this.dragster.reset();
|
||||
$(el).unbind('dragster:enter dragster:leave');
|
||||
}
|
||||
|
||||
if (this.dropzone) {
|
||||
this.dropzone.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
_setValidFileExtensions: function (list) {
|
||||
return RegExp('(\.|\/)(' + list.join('|') + ')$', 'i');
|
||||
},
|
||||
|
||||
_onTextChanged: function () {
|
||||
var value = this.$('.js-textInput').val();
|
||||
if (!value) {
|
||||
this._hideTextError();
|
||||
}
|
||||
},
|
||||
|
||||
_onFileChanged: function (files) {
|
||||
this.trigger('fileSelected', this);
|
||||
|
||||
if (files && files.length === 1) {
|
||||
files = files[0];
|
||||
}
|
||||
|
||||
this.model.setUpload({
|
||||
type: 'file',
|
||||
value: files
|
||||
});
|
||||
|
||||
if (this.model.get('state') !== 'error') {
|
||||
this._hideFileError();
|
||||
this.model.set('state', 'selected');
|
||||
} else {
|
||||
this._showFileError();
|
||||
}
|
||||
},
|
||||
|
||||
_showTextError: function () {
|
||||
this.$('.Form-inputError').addClass('is-visible');
|
||||
},
|
||||
|
||||
_hideTextError: function () {
|
||||
this.$('.Form-inputError').removeClass('is-visible');
|
||||
},
|
||||
|
||||
_showDropzone: function () {
|
||||
this.$('.Form-upload').addClass('is-dropping');
|
||||
this._hideFileError();
|
||||
},
|
||||
|
||||
_hideDropzone: function () {
|
||||
this.$('.Form-upload').removeClass('is-dropping');
|
||||
},
|
||||
|
||||
_showFileError: function () {
|
||||
if (this.model.get('state') === 'error') {
|
||||
this.$('.js-fileError')
|
||||
.text(this.model.get('get_error_text').what_about)
|
||||
.show();
|
||||
this.$('.js-fileLabel').hide();
|
||||
this.$('.js-fileButton').addClass('Button--negative');
|
||||
}
|
||||
},
|
||||
|
||||
_hideFileError: function () {
|
||||
this.$('.js-fileError').hide();
|
||||
this.$('.js-fileLabel').show();
|
||||
this.$('.js-fileButton').removeClass('Button--negative');
|
||||
},
|
||||
|
||||
_onSubmitForm: function (e) {
|
||||
if (e) this.killEvent(e);
|
||||
|
||||
var value = this.$('.js-textInput').val();
|
||||
|
||||
if (!value) {
|
||||
this._hideTextError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Change file attributes :S
|
||||
this.trigger('urlSelected', this);
|
||||
|
||||
// Change model
|
||||
var importType = this.model.get('service_name') ? 'service' : 'url';
|
||||
this.model.setUpload({
|
||||
type: importType,
|
||||
value: value,
|
||||
service_item_id: value,
|
||||
state: 'idle'
|
||||
});
|
||||
|
||||
if (this.model.get('state') !== 'error') {
|
||||
this._hideFileError();
|
||||
this._hideTextError();
|
||||
this.model.set('state', 'selected');
|
||||
|
||||
this.trigger('urlSubmitted', this);
|
||||
} else {
|
||||
this._showTextError();
|
||||
}
|
||||
},
|
||||
|
||||
_checkVisibility: function () {
|
||||
var state = this.model.get('state');
|
||||
this[ state !== 'selected' ? 'show' : 'hide' ]();
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._destroyDropzone();
|
||||
this.$('.js-fileInput').unbind('change');
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
<form class="Form js-form">
|
||||
<div class="Form-row Form-row--centered">
|
||||
<% if (fileEnabled) { %>
|
||||
<div class="Form-rowData Form-rowData--med Form-rowData--noMargin js-dropzone">
|
||||
<div class="Form-upload">
|
||||
<label class="Form-fileLabel js-fileLabel CDB-Text CDB-Size-medium"><%- _t('components.modals.add-layer.imports.form-import.drag-and-drop') %></label>
|
||||
<label class="Form-fileLabel Form-fileLabel--error CDB-Text CDB-Size-small js-fileError"></label>
|
||||
<div class="Form-file">
|
||||
<input type="file" class="js-fileInput" />
|
||||
<span class="CDB-Button CDB-Button--primary Form-fileButton CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase js-fileButton">
|
||||
<%- _t('components.modals.add-layer.imports.form-import.browse') %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="u-lSpace--xl u-rSpace--xl u-flex u-alignCenter CDB-Text CDB-Size-medium u-altTextColor"><%- _t('components.modals.add-layer.imports.form-import.or') %></span>
|
||||
<% } %>
|
||||
<div class="Form-rowData Form-rowData--noMargin Form-rowData--med">
|
||||
<input type="text" class="Form-input Form-input--med has-submit js-textInput CDB-Text CDB-Size-medium" value="" placeholder="https://carto.com/data-library" />
|
||||
<button type="submit" class="CDB-Text CDB-Size-small Form-inputSubmit u-upperCase u-actionTextColor Form-inputSubmit">
|
||||
<span><%- _t('components.modals.add-layer.imports.form-import.submit') %></span>
|
||||
</button>
|
||||
<div class="Form-inputError CDB-Text"><%- _t('components.modals.add-layer.imports.form-import.error-desc') %></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,58 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./import-data-header.tpl');
|
||||
|
||||
/**
|
||||
* Data header view
|
||||
*
|
||||
* - It will change when upload state changes
|
||||
* - Possibility to change state with a header button
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-back': '_goToStart'
|
||||
},
|
||||
|
||||
options: {
|
||||
fileEnabled: false,
|
||||
acceptSync: false
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
|
||||
this._userModel = opts.userModel;
|
||||
this.template = opts.template || template;
|
||||
this._initBinds();
|
||||
this._checkVisibility();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var acceptSync = this.options.acceptSync && this._userModel.get('actions') && this._userModel.isActionEnabled('sync_tables') && this.model.get('type') !== 'file';
|
||||
|
||||
this.$el.html(
|
||||
this.template({
|
||||
type: this.model.get('type'),
|
||||
fileEnabled: this.options.fileEnabled,
|
||||
acceptSync: acceptSync,
|
||||
state: this.model.get('state')
|
||||
})
|
||||
);
|
||||
this._checkVisibility();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:state', this.render, this);
|
||||
},
|
||||
|
||||
_checkVisibility: function () {
|
||||
this.show();
|
||||
},
|
||||
|
||||
_goToStart: function () {
|
||||
this.model.set('state', 'idle');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<% if (state === 'selected') { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.file-selected') %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.upload-file-url', { smart_count: fileEnabled ? 1 : 0 }) %>
|
||||
<% } %>
|
||||
</h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<% if (state !== "selected") { %>
|
||||
<% fileEnabledText = _t('components.modals.add-layer.imports.header-import.select-a-file') +
|
||||
' <a target="_blank" href="https://carto.com/developers/import-api/guides/importing-geospatial-data/#supported-geospatial-data-formats">' +
|
||||
_t('components.modals.add-layer.imports.header-import.see-all-formats') +
|
||||
'</a>'
|
||||
%>
|
||||
<%= _t('components.modals.add-layer.imports.header-import.paste-url', {
|
||||
fileEnabled: fileEnabled ? fileEnabledText : ''
|
||||
}) %>
|
||||
<% } %>
|
||||
<% if (state === "selected") { %>
|
||||
<% if (acceptSync) { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.sync-enabled') %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.sync-disabled') %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</p>
|
||||
<% if (state === "selected") { %>
|
||||
<button class="NavButton NavButton--back ImportPanel-headerButton js-back">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev"></i>
|
||||
</button>
|
||||
<% } %>
|
||||
@@ -0,0 +1,161 @@
|
||||
var ImportView = require('builder/components/modals/add-layer/content/imports/import-view');
|
||||
var UploadModel = require('builder/data/upload-model');
|
||||
var FormView = require('./import-data-form-view');
|
||||
var HeaderView = require('./import-data-header-view');
|
||||
var SelectedDatasetView = require('builder/components/modals/add-layer/content/imports/import-selected-dataset-view');
|
||||
var template = require('./import-data.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
/**
|
||||
* Import data panel
|
||||
*
|
||||
* - It accepts an url
|
||||
* - It checks if it is valid
|
||||
*
|
||||
*/
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'configModel',
|
||||
'createModel',
|
||||
'privacyModel',
|
||||
'guessingModel'
|
||||
];
|
||||
|
||||
module.exports = ImportView.extend({
|
||||
options: {
|
||||
fileExtensions: [],
|
||||
type: 'url',
|
||||
service: '',
|
||||
acceptSync: false,
|
||||
fileEnabled: false,
|
||||
formTemplate: '',
|
||||
headerTemplate: '',
|
||||
fileAttrs: {}
|
||||
},
|
||||
|
||||
className: 'ImportPanel ImportDataPanel',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initModels();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
this._initViews();
|
||||
this._initBinds();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this.model = new UploadModel({
|
||||
type: this.options.type,
|
||||
service_name: this.options.service
|
||||
}, {
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this.model.setFresh();
|
||||
|
||||
var headerView = new HeaderView({
|
||||
el: this.$('.ImportPanel-header'),
|
||||
model: this.model,
|
||||
userModel: this._userModel,
|
||||
fileEnabled: this.options.fileEnabled,
|
||||
acceptSync: this.options.acceptSync,
|
||||
template: this.options.headerTemplate
|
||||
});
|
||||
headerView.render();
|
||||
this.addView(headerView);
|
||||
|
||||
var selected = new SelectedDatasetView({
|
||||
el: this.$('.DatasetSelected'),
|
||||
userModel: this._userModel,
|
||||
model: this.model,
|
||||
acceptSync: this.options.acceptSync,
|
||||
fileAttrs: this.options.fileAttrs,
|
||||
configModel: this._configModel
|
||||
});
|
||||
selected.render();
|
||||
this.addView(selected);
|
||||
|
||||
var formView = new FormView({
|
||||
el: this.$('.ImportPanel-form'),
|
||||
userModel: this._userModel,
|
||||
model: this.model,
|
||||
template: this.options.formTemplate,
|
||||
fileEnabled: this.options.fileEnabled
|
||||
});
|
||||
|
||||
formView.bind('fileSelected', function () {
|
||||
selected.setOptions({
|
||||
acceptSync: false,
|
||||
fileAttrs: {
|
||||
ext: true,
|
||||
title: 'name',
|
||||
description: {
|
||||
content: [{
|
||||
name: 'size',
|
||||
format: 'size'
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
formView.bind('urlSelected', function () {
|
||||
selected.setOptions({
|
||||
acceptSync: true,
|
||||
fileAttrs: {
|
||||
ext: false,
|
||||
title: '',
|
||||
description: ''
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
formView.bind('urlSubmitted', function () {
|
||||
this._finish();
|
||||
}.bind(this));
|
||||
|
||||
formView.render();
|
||||
this.addView(formView);
|
||||
},
|
||||
|
||||
_finish: function () {
|
||||
if (this._createModel.canFinish()) {
|
||||
this.model.setPrivacy(this._privacyModel.get('privacy'));
|
||||
this.model.setGuessing(this._guessingModel.get('guessing'));
|
||||
|
||||
this._createModel.finish();
|
||||
}
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.unbind('change:state', this._checkState, this);
|
||||
this.model.unbind('change', this._triggerChange, this);
|
||||
|
||||
this.model.bind('change:state', this._checkState, this);
|
||||
this.model.bind('change', this._triggerChange, this);
|
||||
},
|
||||
|
||||
_checkState: function () {
|
||||
if (this.model.previous('state') === 'selected') {
|
||||
this.model.set({
|
||||
type: undefined,
|
||||
value: '',
|
||||
service_name: '',
|
||||
service_item_id: '',
|
||||
interval: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="ImportPanel-header">
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m"><%- _t('components.modals.add-layer.imports.form-import.title') %></h3>
|
||||
<p class="ImportPanel-headerDescription"><%- _t('components.modals.add-layer.imports.form-import.desc') %></p>
|
||||
</div>
|
||||
<div class="ImportPanel-body">
|
||||
<div class="ImportPanel-bodyWrapper">
|
||||
<div class="ImportPanel-state ImportPanel-form is-idle"></div>
|
||||
<div class="ImportPanel-state is-selected DatasetSelected"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
<% if (state !== 'list' ) { %>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<% if (state === 'selected') { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.campaign-selected', { brand: 'MailChimp' }) %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.map-campaign', { brand: 'MailChimp' }) %>
|
||||
<% } %>
|
||||
</h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor <% if (state === "error") { %>ImportPanel-headerDescription--negative<% } %>">
|
||||
<% if (state === "idle") { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.state-idle', { brand: 'MailChimp' }) %>
|
||||
<% } %>
|
||||
<% if (state === "error") { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.state-error', { brand: 'MailChimp' }) %>
|
||||
<% } %>
|
||||
<% if (state === "token") { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.state-token', { brand: 'MailChimp' }) %>
|
||||
<% } %>
|
||||
<% if (state === "oauth") { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.state-oauth', { brand: 'MailChimp' }) %>
|
||||
<% } %>
|
||||
<% if (state === "retrieving") { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.state-retrieving', { brand: 'MailChimp' }) %>
|
||||
<% } %>
|
||||
<% if (state === "selected") { %>
|
||||
<%- _t('components.modals.add-layer.imports.mailchimp.state-selected', { brand: 'MailChimp' }) %>
|
||||
<% } %>
|
||||
</p>
|
||||
<% if (state === "selected") { %>
|
||||
<button class="NavButton NavButton--back ImportPanel-headerButton js-back">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev"></i>
|
||||
</button>
|
||||
<% } %>
|
||||
<% } %>
|
||||
@@ -0,0 +1,187 @@
|
||||
var ImportDataView = require('./import-data/import-data-view');
|
||||
var ImportServiceView = require('./import-service/import-service-view');
|
||||
var ImportArcGISView = require('./import-arcgis/import-arcgis-view');
|
||||
var ImportTwitterView = require('./import-twitter/import-twitter-view');
|
||||
|
||||
/**
|
||||
* Attributes:
|
||||
*
|
||||
* view: import pane class view
|
||||
* enabled: function that takes configModel and returns whether the service is enabled
|
||||
* fallbackClassName: ...
|
||||
* name: local name
|
||||
* title: text for tab link
|
||||
* options:
|
||||
* - service:
|
||||
* - fileExtensions:
|
||||
* - showAvailableFormats:
|
||||
* - acceptSync:
|
||||
* - fileAttrs:
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
File: {
|
||||
view: ImportDataView,
|
||||
enabled: function (config, userModel) { return true; },
|
||||
name: 'file',
|
||||
title: 'Data file',
|
||||
options: {
|
||||
type: 'url',
|
||||
fileEnabled: true,
|
||||
acceptSync: true
|
||||
}
|
||||
},
|
||||
GDrive: {
|
||||
view: ImportServiceView,
|
||||
enabled: function (config, userModel) { return !!config.get('oauth_gdrive'); },
|
||||
name: 'gdrive',
|
||||
title: 'Google Drive',
|
||||
options: {
|
||||
service: 'gdrive',
|
||||
fileExtensions: ['Google SpreadSheet', 'CSV'],
|
||||
showAvailableFormats: false,
|
||||
acceptSync: true,
|
||||
fileAttrs: {
|
||||
ext: true,
|
||||
title: 'filename',
|
||||
description: {
|
||||
content: [{
|
||||
name: 'size',
|
||||
format: 'size',
|
||||
key: true
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Dropbox: {
|
||||
view: ImportServiceView,
|
||||
enabled: function (config, userModel) { return !!config.get('oauth_dropbox'); },
|
||||
name: 'dropbox',
|
||||
title: 'Dropbox',
|
||||
options: {
|
||||
service: 'dropbox',
|
||||
fileExtensions: ['CSV', 'XLS'],
|
||||
showAvailableFormats: false,
|
||||
acceptSync: true,
|
||||
fileAttrs: {
|
||||
ext: true,
|
||||
title: 'filename',
|
||||
description: {
|
||||
content: [
|
||||
{
|
||||
name: 'id',
|
||||
format: ''
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
format: 'size',
|
||||
key: true
|
||||
}
|
||||
],
|
||||
separator: '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Box: {
|
||||
view: ImportServiceView,
|
||||
enabled: function (config, userModel) { return !!config.get('oauth_box'); },
|
||||
name: 'box',
|
||||
title: 'Box',
|
||||
fallback: require('./fallbacks/import-box-fallback.tpl'),
|
||||
options: {
|
||||
service: 'box',
|
||||
fileExtensions: ['CSV', 'XLS'],
|
||||
showAvailableFormats: false,
|
||||
acceptSync: true,
|
||||
fileAttrs: {
|
||||
ext: true,
|
||||
title: 'filename',
|
||||
description: {
|
||||
content: [
|
||||
{
|
||||
name: 'size',
|
||||
format: 'size',
|
||||
key: true
|
||||
}
|
||||
],
|
||||
separator: '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Twitter: {
|
||||
view: ImportTwitterView,
|
||||
enabled: function (config, userModel) { return userModel.get('twitter').enabled && !!config.get('datasource_search_twitter'); },
|
||||
fallback: require('./fallbacks/import-twitter-fallback.tpl'),
|
||||
name: 'twitter',
|
||||
title: 'Twitter'
|
||||
},
|
||||
Mailchimp: {
|
||||
view: ImportServiceView,
|
||||
enabled: function (config, userModel) { return userModel.get('mailchimp').enabled && !!config.get('oauth_mailchimp'); },
|
||||
fallback: require('./fallbacks/import-mailchimp-fallback.tpl'),
|
||||
name: 'mailchimp',
|
||||
title: 'MailChimp',
|
||||
options: {
|
||||
service: 'mailchimp',
|
||||
fileExtensions: [],
|
||||
acceptSync: true,
|
||||
showAvailableFormats: false,
|
||||
headerTemplate: require('./import-mailchimp/import-data-header-mailchimp.tpl'),
|
||||
fileAttrs: {
|
||||
ext: true,
|
||||
title: 'filename',
|
||||
description: {
|
||||
content: [{
|
||||
name: 'member_count',
|
||||
format: 'number',
|
||||
key: true
|
||||
}],
|
||||
itemName: 'member',
|
||||
separator: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Instagram: {
|
||||
// view: ImportServiceView,
|
||||
// fallback: require('./fallbacks/import-instagram-fallback.tpl'),
|
||||
// name: 'instagram',
|
||||
// title: 'Instagram',
|
||||
// options: {
|
||||
// service: 'instagram',
|
||||
// fileExtensions: [],
|
||||
// acceptSync: false,
|
||||
// showAvailableFormats: false,
|
||||
// fileAttrs: {
|
||||
// ext: false,
|
||||
// title: 'title'
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
Arcgis: {
|
||||
view: ImportArcGISView,
|
||||
enabled: function (config, userModel) { return config.get('arcgis_enabled'); },
|
||||
fallback: require('./fallbacks/import-arcgis-fallback.tpl'),
|
||||
name: 'arcgis',
|
||||
title: 'ArcGIS Server™'
|
||||
},
|
||||
Salesforce: {
|
||||
view: ImportDataView,
|
||||
enabled: function (config, userModel) { return config.get('salesforce_enabled'); },
|
||||
fallback: require('./fallbacks/import-salesforce-fallback.tpl'),
|
||||
name: 'salesforce',
|
||||
title: 'Salesforce'
|
||||
// options: {
|
||||
// type: 'service',
|
||||
// service_name: 'salesforce',
|
||||
// acceptSync: true,
|
||||
// formTemplate: require('./import-salesforce/import-data-form-salesforce.tpl'),
|
||||
// headerTemplate: require('./import-salesforce/import-data-header-salesforce.tpl')
|
||||
// }
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
<form class="Form js-form">
|
||||
<div class="Form-row">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium"><span><%- _t('components.modals.add-layer.imports.form-import.title') %></span></label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<input type="text" class="CDB-Text CDB-Size-medium Form-input Form-input--longer has-submit js-textInput" value="" placeholder="<%- _t('components.modals.add-layer.imports.salesforce.salesforce.input-placeholder', { brand: 'Salesforce' }) %>" />
|
||||
<button type="submit" class="CDB-Text CDB-Size-small Form-inputSubmit u-upperCase u-actionTextColor Form-inputSubmit">
|
||||
<span><%- _t('components.modals.add-layer.imports.form-import.submit') %></span>
|
||||
</button>
|
||||
<div class="Form-inputError CDB-Text">
|
||||
<span><%- _t('components.modals.add-layer.imports.form-import.error-desc') %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,23 @@
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<% if (state === 'selected') { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.type-selected', { brand: 'Salesforce'}) %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.type-import', { brand: 'Salesforce'}) %>
|
||||
<% } %>
|
||||
</h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<% if (state !== "selected") { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.import-url', { brand: 'Salesforce'}) %>
|
||||
<% } else { %>
|
||||
<% if (acceptSync) { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.sync-enabled', { brand: 'Salesforce'}) %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.header-import.sync-disabled', { brand: 'Salesforce'}) %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</p>
|
||||
<% if (state === "selected") { %>
|
||||
<button class="NavButton NavButton--back ImportPanel-headerButton js-back">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev"></i>
|
||||
</button>
|
||||
<% } %>
|
||||
@@ -0,0 +1,168 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var template = require('./import-selected-dataset.tpl');
|
||||
|
||||
/**
|
||||
* Selected dataset
|
||||
*
|
||||
* - Displays the result when a dataset is selected, no matter the type.
|
||||
* - It will show available sync options if that import lets it.
|
||||
* - Upgrade link for people who don't have sync permissions.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'DatasetSelected',
|
||||
|
||||
_FORMATTERS: {
|
||||
'size': Utils.readablizeBytes,
|
||||
'number': Utils.formatNumber
|
||||
},
|
||||
|
||||
options: {
|
||||
acceptSync: false,
|
||||
fileAttrs: {
|
||||
ext: false,
|
||||
title: '',
|
||||
description: {
|
||||
content: [{
|
||||
name: 'id',
|
||||
format: ''
|
||||
}],
|
||||
itemName: '',
|
||||
separator: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .js-interval-0': '_onIntervalZero',
|
||||
'click .js-interval-1': '_onIntervalHour',
|
||||
'click .js-interval-2': '_onIntervalDay',
|
||||
'click .js-interval-3': '_onIntervalWeek',
|
||||
'click .js-interval-4': '_onIntervalMonth'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.userModel) throw new TypeError('userModel is required');
|
||||
if (!opts.configModel) throw new TypeError('configModel is required');
|
||||
|
||||
this._configModel = opts.configModel;
|
||||
this._userModel = opts.userModel;
|
||||
this._initBinds();
|
||||
this._checkVisibility();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var title = this.options.fileAttrs.title && this.model.get('value')[this.options.fileAttrs.title] || this.model.get('value');
|
||||
var description = this._genDescription();
|
||||
var ext = this.options.fileAttrs.ext ? Utils.getFileExtension(title) : '';
|
||||
|
||||
if (this.options.fileAttrs.ext) {
|
||||
title = title && title.replace('.' + ext, '');
|
||||
}
|
||||
|
||||
var upgradeUrl = window.upgrade_url;
|
||||
var userCanSync = this._userModel.isActionEnabled('sync_tables');
|
||||
var customInstall = this._configModel.get('cartodb_com_hosted');
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
title: title,
|
||||
description: description,
|
||||
ext: ext,
|
||||
interval: this.model.get('interval'),
|
||||
importCanSync: this.options.acceptSync,
|
||||
userCanSync: userCanSync,
|
||||
showTrial: this._userModel.canStartTrial(),
|
||||
showUpgrade: !userCanSync && !customInstall && upgradeUrl && !this._userModel.isInsideOrg(),
|
||||
upgradeUrl: upgradeUrl
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:value', this.render, this);
|
||||
this.model.bind('change:interval', this.render, this);
|
||||
this.model.bind('change:state', this._checkVisibility, this);
|
||||
},
|
||||
|
||||
_genDescription: function () {
|
||||
if (this.options.fileAttrs && this.options.fileAttrs.description) {
|
||||
var descriptionOpts = this.options.fileAttrs.description;
|
||||
var descriptionKeyValue = '';
|
||||
var descriptionStr = '';
|
||||
var self = this;
|
||||
|
||||
if (descriptionOpts.content && descriptionOpts.content.length > 0) {
|
||||
_.each(descriptionOpts.content, function (item, i) {
|
||||
if (i > 0 && descriptionOpts.separator) {
|
||||
descriptionStr += ' ' + descriptionOpts.separator + ' ';
|
||||
}
|
||||
|
||||
var value = self.model.get('value')[item.name];
|
||||
var format = item.format && self._FORMATTERS[item.format];
|
||||
descriptionStr += format && format(value) || value;
|
||||
|
||||
if (item.key) {
|
||||
descriptionKeyValue = item.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (descriptionOpts.itemName && descriptionKeyValue) {
|
||||
descriptionStr += ' ' + (descriptionOpts.itemName && _t('components.modals.add-layer.imports.' + descriptionOpts.itemName + '-pluralize', { smart_count: descriptionKeyValue }) || '');
|
||||
}
|
||||
|
||||
return descriptionStr;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
||||
_onIntervalZero: function () {
|
||||
this.model.set('interval', 0);
|
||||
},
|
||||
|
||||
_onIntervalHour: function () {
|
||||
if (this.options.acceptSync && this._userModel.isActionEnabled('sync_tables')) {
|
||||
this.model.set('interval', 3600);
|
||||
}
|
||||
},
|
||||
|
||||
_onIntervalDay: function () {
|
||||
if (this.options.acceptSync && this._userModel.isActionEnabled('sync_tables')) {
|
||||
this.model.set('interval', 86400);
|
||||
}
|
||||
},
|
||||
|
||||
_onIntervalWeek: function () {
|
||||
if (this.options.acceptSync && this._userModel.isActionEnabled('sync_tables')) {
|
||||
this.model.set('interval', 604800);
|
||||
}
|
||||
},
|
||||
|
||||
_onIntervalMonth: function () {
|
||||
if (this.options.acceptSync && this._userModel.isActionEnabled('sync_tables')) {
|
||||
this.model.set('interval', 2592000);
|
||||
}
|
||||
},
|
||||
|
||||
setOptions: function (d) {
|
||||
if (d && !_.isEmpty(d)) {
|
||||
_.extend(this.options, d);
|
||||
}
|
||||
},
|
||||
|
||||
_checkVisibility: function () {
|
||||
var state = this.model.get('state');
|
||||
if (state === 'selected') {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
<div class="DatasetSelected-item">
|
||||
<div class="DatasetSelected-itemExt CDB-Text u-ellipsis">
|
||||
<%- ext || '?' %>
|
||||
</div>
|
||||
<div class="DatasetSelected-itemInfo u-ellipsis">
|
||||
<h6 class="CDB-Text CDB-Size-large u-ellipsis" title="<%- title %>"><%- title %></h6>
|
||||
<p class="CDB-Text CDB-Size-small u-ellipsis u-altTextColor"><%- description %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% if (importCanSync) { %>
|
||||
<div class="DatasetSelected-sync">
|
||||
<div class="DatasetSelected-syncOptions">
|
||||
<label class="DatasetSelected-syncLabel CDB-Text CDB-Size-medium u-mainTextColor">
|
||||
<%- _t('components.modals.add-layer.imports.selected-state.sync-my-data') %>
|
||||
</label>
|
||||
<ul class="DatasetSelected-syncOptionsList">
|
||||
<li class="DatasetSelected-syncOptionsItem">
|
||||
<div class="RadioButton">
|
||||
<button class="RadioButton-input js-interval-0 <%- interval === 0 ? 'is-checked' : '' %>"></button>
|
||||
<label class="CDB-Text CDB-Size-medium u-altTextColor u-lSpace"><%- _t('components.modals.add-layer.imports.selected-state.never') %></label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="DatasetSelected-syncOptionsItem">
|
||||
<div class="RadioButton <%- !userCanSync ? 'is-disabled' : '' %>">
|
||||
<button class="RadioButton-input js-interval-1 <%- interval === 3600 ? 'is-checked' : '' %>"></button>
|
||||
<label class="CDB-Text CDB-Size-medium u-altTextColor u-lSpace"><%- _t('components.modals.add-layer.imports.selected-state.every-hour') %></label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="DatasetSelected-syncOptionsItem">
|
||||
<div class="RadioButton <%- !userCanSync ? 'is-disabled' : '' %>">
|
||||
<button class="RadioButton-input js-interval-2 <%- interval === 86400 ? 'is-checked' : '' %>"></button>
|
||||
<label class="CDB-Text CDB-Size-medium u-altTextColor u-lSpace"><%- _t('components.modals.add-layer.imports.selected-state.every-day') %></label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="DatasetSelected-syncOptionsItem">
|
||||
<div class="RadioButton <%- !userCanSync ? 'is-disabled' : '' %>">
|
||||
<button class="RadioButton-input js-interval-3 <%- interval === 604800 ? 'is-checked' : '' %>"></button>
|
||||
<label class="CDB-Text CDB-Size-medium u-altTextColor u-lSpace"><%- _t('components.modals.add-layer.imports.selected-state.every-week') %></label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="DatasetSelected-syncOptionsItem">
|
||||
<div class="RadioButton <%- !userCanSync ? 'is-disabled' : '' %>">
|
||||
<button class="RadioButton-input js-interval-4 <%- interval === 2592000 ? 'is-checked' : '' %>"></button>
|
||||
<label class="CDB-Text CDB-Size-medium u-altTextColor u-lSpace"><%- _t('components.modals.add-layer.imports.selected-state.every-month') %></label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% if (showUpgrade) { %>
|
||||
<div class="Upgrade-info">
|
||||
<p class="CDB-Text CDB-Size-medium u-ellipsis u-secondaryTextColor">
|
||||
<% featuresLink = '<a href="https://carto.com/pricing">' + _t('components.modals.add-layer.imports.selected-state.more-features') + '</a>' %>
|
||||
<%- _t('components.modals.add-layer.imports.selected-state.upgrade-desc', { features: featuresLink }) %>
|
||||
</p>
|
||||
<div class="Upgrade-infoActions">
|
||||
<% if (showTrial) { %>
|
||||
<p class="CDB-Text CDB-Size-medium u-ellipsis is-semibold u-rSpace--xl"><%- _t('components.modals.add-layer.imports.selected-state.free-trial', { days: 14 }) %></p>
|
||||
<% } %>
|
||||
<a href="<%- upgradeUrl %>" class="CDB-Button CDB-Button--secondary">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase"><%- _t('components.modals.add-layer.imports.selected-state.upgrade') %></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
@@ -0,0 +1,61 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./import-service-header.tpl');
|
||||
|
||||
/**
|
||||
* Service header
|
||||
*
|
||||
* - It will change when upload state changes
|
||||
* - Possibility to change state with a header button
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-back': '_goToList'
|
||||
},
|
||||
|
||||
options: {
|
||||
title: 'Service',
|
||||
showAvailableFormats: false,
|
||||
acceptSync: false,
|
||||
fileExtensions: [],
|
||||
template: ''
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
this._userModel = opts.userModel;
|
||||
this.template = opts.template || template;
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
this.template({
|
||||
items: this.collection.size(),
|
||||
service_name: this.model.get('service_name'),
|
||||
showAvailableFormats: this.options.showAvailableFormats,
|
||||
fileExtensions: this.options.fileExtensions,
|
||||
acceptSync: this.options.acceptSync && this._userModel.isActionEnabled('sync_tables'),
|
||||
state: this.model.get('state'),
|
||||
title: this.options.title
|
||||
})
|
||||
);
|
||||
this._checkVisibility();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:state', this.render, this);
|
||||
},
|
||||
|
||||
_checkVisibility: function () {
|
||||
var state = this.model.get('state');
|
||||
this[ state !== 'list' ? 'show' : 'hide' ]();
|
||||
},
|
||||
|
||||
_goToList: function () {
|
||||
this.model.set('state', 'list');
|
||||
}
|
||||
|
||||
});
|
||||