Initial commit
This commit is contained in:
+251
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
Executable
+57
@@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
Executable
+116
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Executable
+73
@@ -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>
|
||||
Executable
+9
@@ -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>
|
||||
Executable
+58
@@ -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');
|
||||
}
|
||||
|
||||
});
|
||||
Executable
+11
@@ -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>
|
||||
Executable
+27
@@ -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>
|
||||
+72
@@ -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'));
|
||||
}
|
||||
|
||||
});
|
||||
Executable
+218
@@ -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;
|
||||
}
|
||||
});
|
||||
Executable
+12
@@ -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>
|
||||
+123
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
Executable
+44
@@ -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>
|
||||
+14
@@ -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>
|
||||
+12
@@ -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>
|
||||
+10
@@ -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>
|
||||
+10
@@ -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>
|
||||
+12
@@ -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>
|
||||
+12
@@ -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>
|
||||
+23
@@ -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>
|
||||
+19
@@ -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>
|
||||
<% } %>
|
||||
+48
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
+64
@@ -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);
|
||||
}
|
||||
});
|
||||
+223
@@ -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);
|
||||
}
|
||||
});
|
||||
+26
@@ -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>
|
||||
+58
@@ -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');
|
||||
}
|
||||
|
||||
});
|
||||
+31
@@ -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>
|
||||
<% } %>
|
||||
+161
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+10
@@ -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>
|
||||
+34
@@ -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>
|
||||
<% } %>
|
||||
<% } %>
|
||||
Executable
+187
@@ -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')
|
||||
// }
|
||||
}
|
||||
};
|
||||
+16
@@ -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>
|
||||
+23
@@ -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>
|
||||
<% } %>
|
||||
+168
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+66
@@ -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>
|
||||
<% } %>
|
||||
+61
@@ -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');
|
||||
}
|
||||
|
||||
});
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<% if (state !== 'list' ) { %>
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<% if (state === 'selected') { %>
|
||||
<% if (service_name === 'instagram') { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.account-connected') %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.item-selected') %>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.connect-with', { title: title }) %>
|
||||
<% } %>
|
||||
</h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor <% if (state === "error") { %>ImportPanel-headerDescription--negative<% } %>">
|
||||
<% if (state === "idle") { %>
|
||||
<% if (fileExtensions.length > 0) { %>
|
||||
<% formatsLink = _t('components.modals.add-layer.imports.service-import.and') + ' <a target="_blank" href="https://docs.carto.com/cartodb-editor/datasets/#supported-file-formats" class="ImportPanel-headerDescriptionLink">' + _t('components.modals.add-layer.imports.service-import.many-more-formats') + '</a>' %>
|
||||
<%- fileExtensions.join(', ') %><% if (showAvailableFormats) { %> <%- formatsLink %> <% } %> <%- _t('components.modals.add-layer.imports.service-import.supported') %>.
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-idle-login') %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% if (state === "error") { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-error', { title: title }) %>
|
||||
<% } %>
|
||||
<% if (state === "token") { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-token') %>
|
||||
<% } %>
|
||||
<% if (state === "oauth") { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-oauth') %>
|
||||
<% } %>
|
||||
<% if (state === "retrieving") { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-retrieving', { title: title }) %>
|
||||
<% } %>
|
||||
<% if (state === "selected") { %>
|
||||
<% if (acceptSync) { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-selected-sync') %>
|
||||
<% } else { %>
|
||||
<% if (service_name === 'instagram') { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-selected-instagram') %>
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.imports.service-import.state-selected-no-sync') %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</p>
|
||||
<% if (state === "selected" && items > 1) { %>
|
||||
<button class="CDB-Size-large ImportPanel-headerButton js-back">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev u-actionTextColor"></i>
|
||||
</button>
|
||||
<% } %>
|
||||
<% } %>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
var Utils = require('builder/helpers/utils');
|
||||
|
||||
/**
|
||||
* Service list item format utils
|
||||
*
|
||||
* - Create customized functions for service list items.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// Due to the fact that backend data source service
|
||||
// returns 0 size when it doesn't know it
|
||||
formatSize: function (s) {
|
||||
if (s && s > 0) {
|
||||
return Utils.readablizeBytes(s);
|
||||
} else {
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* Service item model
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
id: '',
|
||||
filename: '',
|
||||
checksum: '',
|
||||
service: '',
|
||||
size: '',
|
||||
title: ''
|
||||
}
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
var ServiceItem = require('./import-service-item-model');
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* Service item model + Service items collection
|
||||
*
|
||||
* - It needs a datasource name or it won't work.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
|
||||
_DATASOURCE_NAME: 'dropbox',
|
||||
|
||||
model: ServiceItem,
|
||||
|
||||
url: function (method) {
|
||||
var version = this._configModel.urlVersion('imports_service');
|
||||
var baseUrl = this._configModel.get('base_url');
|
||||
return baseUrl + '/api/' + version + '/imports/service/' + this._DATASOURCE_NAME + '/list_files';
|
||||
},
|
||||
|
||||
initialize: function (coll, opts) {
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
if (!opts.datasourceName) throw new Error('datasourceName is required');
|
||||
this._configModel = opts.configModel;
|
||||
this._DATASOURCE_NAME = opts.datasourceName;
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
this.trigger('fetch', this);
|
||||
return Backbone.Collection.prototype.fetch.apply(this, arguments);
|
||||
},
|
||||
|
||||
parse: function (r) {
|
||||
return r.files;
|
||||
}
|
||||
|
||||
});
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var ServiceUtilsFormat = require('./import-service-item-description-format');
|
||||
var template = require('./import-service-list-item.tpl');
|
||||
var _ = require('underscore');
|
||||
|
||||
/**
|
||||
* Service list item view
|
||||
*
|
||||
* - Displays the item info.
|
||||
* - It lets user to select the item for a future import.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
options: {
|
||||
title: '',
|
||||
fileAttrs: {
|
||||
ext: false,
|
||||
title: 'filename',
|
||||
description: 'size',
|
||||
itemName: 'file',
|
||||
formatDescription: ''
|
||||
}
|
||||
},
|
||||
|
||||
_FORMATTERS: {
|
||||
'size': ServiceUtilsFormat.formatSize,
|
||||
'number': Utils.formatNumber
|
||||
},
|
||||
|
||||
className: 'ServiceList-item',
|
||||
tagName: 'li',
|
||||
|
||||
events: {
|
||||
'click .js-choose': '_onSelectItem'
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var title = this.model.get(this.options.fileAttrs.title);
|
||||
var description = this._genDescription();
|
||||
var ext = this.options.fileAttrs.ext ? Utils.getFileExtension(title) : '';
|
||||
|
||||
if (this.options.fileAttrs.ext) {
|
||||
title = title && title.replace('.' + ext, '');
|
||||
}
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
name: this.options.title,
|
||||
ext: ext,
|
||||
title: title,
|
||||
description: description
|
||||
})
|
||||
);
|
||||
return 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(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 '';
|
||||
},
|
||||
|
||||
_onSelectItem: function () {
|
||||
this.trigger('selected', this.model, this);
|
||||
}
|
||||
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<div class="ServiceList-itemExt u-ellipsLongText CDB-Text CDB-Size-medium u-rSpace--xl">
|
||||
<%- ext || '?' %>
|
||||
</div>
|
||||
<div class="ServiceList-itemInfo u-flex u-alignCenter u-justifySpace">
|
||||
<div class="ServiceList-itemInfoTitle">
|
||||
<h6 class="CDB-Text CDB-Size-large u-bSpace u-ellipsis" ><%- title %></h6>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-ellipsis"><%- description %></p>
|
||||
</div>
|
||||
<div class="ServiceList-itemActions">
|
||||
<button class="CDB-Button CDB-Button--secondary js-choose">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase"><%- _t('components.modals.add-layer.imports.service-import.choose') %></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var ServiceListItemView = require('./import-service-list-item-view');
|
||||
var template = require('./import-service-list.tpl');
|
||||
|
||||
/**
|
||||
* Service list view
|
||||
*
|
||||
* - It will display all the items available under
|
||||
* the service and the possibility to chose one of
|
||||
* them.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
options: {
|
||||
title: 'service',
|
||||
fileAttrs: {}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
this._checkVisibility();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
var size = this.collection.size();
|
||||
this.$el.html(
|
||||
template({
|
||||
size: size,
|
||||
title: this.options.title,
|
||||
pluralize: _t('components.modals.add-layer.imports.item-pluralize', { smart_count: size })
|
||||
})
|
||||
);
|
||||
if (this.collection.size() > 0) {
|
||||
this.collection.each(this._addItem, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.collection.bind('sync', this.render, this);
|
||||
this.model.bind('change:state', this._checkVisibility, this);
|
||||
this.add_related_model(this.collection);
|
||||
},
|
||||
|
||||
_addItem: function (m) {
|
||||
var item = new ServiceListItemView({
|
||||
model: m,
|
||||
title: this.options.title,
|
||||
fileAttrs: this.options.fileAttrs
|
||||
});
|
||||
item.bind('selected', this._onSelectedItem, this);
|
||||
this.$('.ServiceList-items').append(item.render().el);
|
||||
this.addView(item);
|
||||
},
|
||||
|
||||
_onSelectedItem: function (mdl) {
|
||||
this.model.setUpload({
|
||||
state: 'selected',
|
||||
value: mdl.toJSON(),
|
||||
service_item_id: mdl.get('id')
|
||||
});
|
||||
},
|
||||
|
||||
_checkVisibility: function () {
|
||||
var state = this.model.get('state');
|
||||
if (state === 'list') {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<div class="ConnectDataset-header">
|
||||
<h2 class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<%- _t('components.modals.add-layer.imports.service-import.found-in', {
|
||||
size: size,
|
||||
pluralize: pluralize,
|
||||
title: title
|
||||
}) %>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="ServiceList-body">
|
||||
<% if (size > 0) { %>
|
||||
<ul class="ServiceList-items"></ul>
|
||||
<% } else { %>
|
||||
<div class="IntermediateInfo ServiceList-empty">
|
||||
<div class="LayoutIcon">
|
||||
<i class="CDB-IconFont CDB-IconFont-lens"></i>
|
||||
</div>
|
||||
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<%- _t('components.modals.add-layer.imports.service-import.no-results-title') %>
|
||||
</h4>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<%- _t('components.modals.add-layer.imports.service-import.no-results-desc') %>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./import-service-loader.tpl');
|
||||
|
||||
/**
|
||||
* Service loader view
|
||||
*
|
||||
* - It will be on charge to make token and oauth petitions
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-connect': '_checkToken'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.serviceTokenModel) throw new Error('serviceTokenModel provider is required');
|
||||
if (!opts.serviceOauthModel) throw new Error('serviceOauthModel is required');
|
||||
|
||||
this._serviceTokenModel = opts.serviceTokenModel;
|
||||
this._serviceOauthModel = opts.serviceOauthModel;
|
||||
this._initBinds();
|
||||
this._checkVisibility();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
state: this.model.get('state')
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:state', function () {
|
||||
this.render();
|
||||
this._checkVisibility();
|
||||
}, this);
|
||||
},
|
||||
|
||||
_checkToken: function () {
|
||||
var self = this;
|
||||
this.model.set('state', 'token');
|
||||
this._serviceTokenModel.fetch({
|
||||
success: function (r) {
|
||||
if (!r.get('oauth_valid')) {
|
||||
self._getOauthURL();
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
self._getOauthURL();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_checkVisibility: function () {
|
||||
var state = this.model.get('state');
|
||||
if (state !== 'list' && state !== 'selected') {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_getOauthURL: function () {
|
||||
var self = this;
|
||||
this.model.set('state', 'oauth');
|
||||
this._serviceOauthModel.set({ url: '' }, { silent: true });
|
||||
this._serviceOauthModel.fetch({
|
||||
error: function () {
|
||||
self.model.set('state', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<div class="ImportPanel-actions">
|
||||
<% if (state === "idle") { %>
|
||||
<button class="CDB-Button CDB-Button--primary js-connect">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">
|
||||
<%- _t('components.modals.add-layer.imports.service-import.connect') %>
|
||||
</span>
|
||||
</button>
|
||||
<% } else if (state === "error") { %>
|
||||
<button class="CDB-Button CDB-Button--primary js-connect">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">
|
||||
<%- _t('components.modals.add-layer.imports.service-import.try-again') %>
|
||||
</span>
|
||||
</button>
|
||||
<% } else { %>
|
||||
<div class="Spinner ImportPanel-actionsLoader"></div>
|
||||
<% } %>
|
||||
</div>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* Get oauth url from the service requested
|
||||
*
|
||||
* - It needs a datasource name or it won't work.
|
||||
*
|
||||
* new ServiceOauthModel({ datasourceName: 'dropbox', configModel: configModel })
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
_DATASOURCE_NAME: 'dropbox',
|
||||
|
||||
url: function (method) {
|
||||
var version = this._configModel.urlVersion('imports_service');
|
||||
var baseUrl = this._configModel.get('base_url');
|
||||
return baseUrl + '/api/' + version + '/imports/service/' + this._DATASOURCE_NAME + '/auth_url';
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
if (!opts.datasourceName) throw new Error('datasource_name is required');
|
||||
this._configModel = opts.configModel;
|
||||
this._DATASOURCE_NAME = opts.datasourceName;
|
||||
}
|
||||
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* Model to check if oAuth token is valid or not
|
||||
*
|
||||
* - It needs a datasource name or it won't work.
|
||||
*
|
||||
* new ServiceTokenModel({ datasourceName: 'dropbox', configModel: configModel })
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
_DATASOURCE_NAME: 'dropbox',
|
||||
|
||||
url: function (method) {
|
||||
var version = this._configModel.urlVersion('imports_service');
|
||||
var baseUrl = this._configModel.get('base_url');
|
||||
return baseUrl + '/api/' + version + '/imports/service/' + this._DATASOURCE_NAME + '/token_valid';
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
if (!opts.datasourceName) throw new Error('datasource_name is required');
|
||||
this._configModel = opts.configModel;
|
||||
this._DATASOURCE_NAME = opts.datasourceName;
|
||||
}
|
||||
|
||||
});
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
var ImportView = require('builder/components/modals/add-layer/content/imports/import-view');
|
||||
var UploadModel = require('builder/data/upload-model');
|
||||
var ServiceHeaderView = require('./import-service-header-view');
|
||||
var ServiceLoaderView = require('./import-service-loader-view');
|
||||
var ServiceListView = require('./import-service-list-view');
|
||||
var ServiceSelectedFileView = require('builder/components/modals/add-layer/content/imports/import-selected-dataset-view');
|
||||
var ServiceTokenModel = require('./import-service-token-model');
|
||||
var ServiceOauthModel = require('./import-service-oauth-model');
|
||||
var ServiceCollection = require('./import-service-items-collection');
|
||||
var template = require('./import-service.tpl');
|
||||
var WINDOW_INTERVAL = 1000; // miliseconds
|
||||
|
||||
/**
|
||||
* Import service view
|
||||
*
|
||||
* - Use a service import panel
|
||||
* - It will request login to the service
|
||||
* - If it works, a list of available files will appear.
|
||||
* - Once a file is selected, sync options will appear.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = ImportView.extend({
|
||||
_DATASOURCE_NAME: 'dropbox',
|
||||
|
||||
className: 'ImportPanel ImportPanelService',
|
||||
|
||||
options: {
|
||||
service: '', // Name of the service
|
||||
showAvailableFormats: false, // If all available format link should appear or not
|
||||
fileExtensions: [], // File extensions
|
||||
acceptSync: false, // Accept sync this service?
|
||||
fileAttrs: { // Attributes or changes for service list or selected file:
|
||||
ext: false, // If files should show extension
|
||||
title: 'filename', // Title attribute
|
||||
description: '<%- size %>', // Description attribute
|
||||
formatDescription: 'size', // If any format function should be applied over the description
|
||||
headerTemplate: '' // Header template
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.service) throw new Error('service prodiver is required');
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
|
||||
this._userModel = opts.userModel;
|
||||
this._configModel = opts.configModel;
|
||||
this._DATASOURCE_NAME = this.options.service;
|
||||
|
||||
this.model = new UploadModel({
|
||||
type: 'service',
|
||||
service_name: this._DATASOURCE_NAME
|
||||
}, {
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._initModels();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template(this.options));
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this._serviceTokenModel = new ServiceTokenModel(null, {
|
||||
datasourceName: this._DATASOURCE_NAME,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this._serviceOauthModel = new ServiceOauthModel(null, {
|
||||
datasourceName: this._DATASOURCE_NAME,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this.collection = new ServiceCollection(null, {
|
||||
datasourceName: this._DATASOURCE_NAME,
|
||||
configModel: this._configModel
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this._triggerChange, this);
|
||||
this.model.bind('change:state', this._checkState, this);
|
||||
this._serviceTokenModel.bind('change:oauth_valid', this._onOauthChange, this);
|
||||
this._serviceOauthModel.bind('change:url', this._openWindow, this);
|
||||
this.add_related_model(this._serviceOauthModel);
|
||||
this.add_related_model(this._serviceTokenModel);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var header = new ServiceHeaderView({
|
||||
el: this.$('.ImportPanel-header'),
|
||||
userModel: this._userModel,
|
||||
model: this.model,
|
||||
collection: this.collection,
|
||||
title: this.options.title,
|
||||
showAvailableFormats: this.options.showAvailableFormats,
|
||||
fileExtensions: this.options.fileExtensions,
|
||||
acceptSync: this.options.acceptSync,
|
||||
template: this.options.headerTemplate
|
||||
});
|
||||
header.render();
|
||||
this.addView(header);
|
||||
|
||||
var loader = new ServiceLoaderView({
|
||||
el: this.$('.ServiceLoader'),
|
||||
model: this.model,
|
||||
serviceTokenModel: this._serviceTokenModel,
|
||||
serviceOauthModel: this._serviceOauthModel
|
||||
});
|
||||
loader.render();
|
||||
this.addView(loader);
|
||||
|
||||
var list = new ServiceListView({
|
||||
el: this.$('.ServiceList'),
|
||||
model: this.model,
|
||||
collection: this.collection,
|
||||
title: this.options.title,
|
||||
fileAttrs: this.options.fileAttrs
|
||||
});
|
||||
list.render();
|
||||
this.addView(list);
|
||||
|
||||
var selected = new ServiceSelectedFileView({
|
||||
el: this.$('.ServiceSelected'),
|
||||
userModel: this._userModel,
|
||||
model: this.model,
|
||||
acceptSync: this.options.acceptSync,
|
||||
fileAttrs: this.options.fileAttrs,
|
||||
configModel: this._configModel
|
||||
});
|
||||
selected.render();
|
||||
this.addView(selected);
|
||||
},
|
||||
|
||||
_onOauthChange: function () {
|
||||
if (this._serviceTokenModel.get('oauth_valid')) {
|
||||
this._getFiles();
|
||||
}
|
||||
},
|
||||
|
||||
_getFiles: function () {
|
||||
var self = this;
|
||||
|
||||
this.model.set('state', 'retrieving');
|
||||
|
||||
this.collection.fetch({
|
||||
error: function () {
|
||||
self.model.set('state', 'error');
|
||||
},
|
||||
success: function () {
|
||||
self.model.set('state', 'list');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_checkState: function () {
|
||||
if (this.model.get('state') === 'list') {
|
||||
if (this.collection.size() === 1) {
|
||||
var item = this.collection.at(0);
|
||||
this.model.setUpload({
|
||||
state: 'selected',
|
||||
value: item.toJSON(),
|
||||
service_item_id: item.get('id')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.model.get('state') !== 'selected') {
|
||||
this.model.set({
|
||||
value: '',
|
||||
service_item_id: '',
|
||||
interval: 0
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_openWindow: function () {
|
||||
var url = this._serviceOauthModel.get('url');
|
||||
var self = this;
|
||||
var i = window.open(url, null, 'menubar=no,toolbar=no,width=600,height=495');
|
||||
var e = window.setInterval(function () {
|
||||
if (i && i.closed) {
|
||||
self._getFiles();
|
||||
clearInterval(e);
|
||||
} else if (!i) {
|
||||
self.model.set('state', 'error');
|
||||
clearInterval(e);
|
||||
}
|
||||
}, WINDOW_INTERVAL);
|
||||
}
|
||||
|
||||
});
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<div class="ImportPanel-header"></div>
|
||||
<div class="ImportPanel-body">
|
||||
<div class="ImportPanel-bodyWrapper">
|
||||
<div class="ImportPanel-state ServiceLoader is-idle is-token is-oauth is-retrieving is-error"></div>
|
||||
<div class="ImportPanel-state ServiceList is-list"></div>
|
||||
<div class="ImportPanel-state ServiceSelected DatasetSelected is-selected"></div>
|
||||
</div>
|
||||
</div>
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
<i class='TabIcon is-<%- name %>'></i>
|
||||
<span><%= cdb.core.sanitize.html(title || name) %></span>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var template = require('./credits-info.tpl');
|
||||
|
||||
/**
|
||||
* Credits info view
|
||||
*
|
||||
* - Percentage of use
|
||||
* - Possible money spent
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
this._userModel = opts.userModel;
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var twitterData = this._userModel.get('twitter');
|
||||
var remaining = twitterData.quota - twitterData.monthly_use;
|
||||
var per = Math.min(100, Math.ceil((this.model.get('value') * 100) / remaining));
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
value: this.model.get('value'),
|
||||
remaining: remaining,
|
||||
per: per,
|
||||
hardLimit: twitterData.hard_limit,
|
||||
remainingFormatted: Utils.formatNumber(remaining),
|
||||
quota: twitterData.quota,
|
||||
block_price: twitterData.block_price,
|
||||
block_size: Utils.readizableNumber(twitterData.block_size)
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this.render, this);
|
||||
}
|
||||
|
||||
});
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<% extraTweets = '<strong>$' + block_price/100 + '/' + block_size + ' ' + _t('components.modals.add-layer.imports.twitter.extra-tweets') + '</strong>' %>
|
||||
<% if (value <= remaining) { %>
|
||||
<%- _t('components.modals.add-layer.imports.twitter.credits-left', {
|
||||
per: per,
|
||||
remainingFormatted: remainingFormatted
|
||||
}) %>
|
||||
<% } else if (remaining <= 0 && !hardLimit) { %>
|
||||
<%= _t('components.modals.add-layer.imports.twitter.credits-consumed', {
|
||||
extraTweets: extraTweets
|
||||
}) %>
|
||||
<% } else { %>
|
||||
<%= _t('components.modals.add-layer.imports.twitter.credits-no-limit', {
|
||||
extraTweets: extraTweets
|
||||
}) %>
|
||||
<% } %>
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
require('jquery-ui');
|
||||
var CreditsInfoView = require('./credits-info-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel'
|
||||
];
|
||||
|
||||
var DEFAULT_PER_VALUE = 80;
|
||||
var MIN_PER_VALUE = 1;
|
||||
|
||||
/**
|
||||
* Set max use of credits for Twitter
|
||||
*
|
||||
* - Slider range = 1000 credits
|
||||
* - Last step should be infinite if user doesn't
|
||||
* have "soft_limit".
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
this._disabled = (opts && opts.disabled) ? opts.disabled : false;
|
||||
|
||||
this.model = new Backbone.Model();
|
||||
this._initBinds();
|
||||
this._setModel();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this._destroySlider();
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:value', this._onValueChange, this);
|
||||
},
|
||||
|
||||
_setModel: function () {
|
||||
var twitterData = this._userModel.get('twitter');
|
||||
var max = twitterData.quota - twitterData.monthly_use;
|
||||
var min = (MIN_PER_VALUE * max) / 100; // Just 1% of the quota
|
||||
var defaultValue = (max * DEFAULT_PER_VALUE) / 100;
|
||||
var value = max > 0 ? defaultValue : (max + 1);
|
||||
|
||||
var self = this;
|
||||
this.model.set({
|
||||
max: (twitterData.hard_limit ? max : max + 1),
|
||||
min: min,
|
||||
step: min,
|
||||
value: max > 0 ? value : twitterData.quota,
|
||||
disabled: self._disabled || max <= 0
|
||||
});
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._setModel();
|
||||
this.$('.js-slider').slider(
|
||||
_.extend({
|
||||
range: 'min',
|
||||
orientation: 'horizontal',
|
||||
slide: this._onSlideChange.bind(this),
|
||||
change: this._onSlideChange.bind(this)
|
||||
},
|
||||
this.model.attributes
|
||||
)
|
||||
);
|
||||
|
||||
var creditsInfo = new CreditsInfoView({
|
||||
el: this.$('.js-info'),
|
||||
userModel: this._userModel,
|
||||
model: this.model
|
||||
});
|
||||
creditsInfo.render();
|
||||
this.addView(creditsInfo);
|
||||
},
|
||||
|
||||
_onSlideChange: function (ev, ui) {
|
||||
this.model.set('value', ui.value);
|
||||
},
|
||||
|
||||
_onValueChange: function () {
|
||||
this.trigger('maxCreditsChange', this.getMaxCredits(), this);
|
||||
},
|
||||
|
||||
getMaxCredits: function () {
|
||||
var twitterData = this._userModel.get('twitter');
|
||||
var max = twitterData.quota - twitterData.monthly_use;
|
||||
var value = this.model.get('value');
|
||||
return value > max ? 0 : value;
|
||||
},
|
||||
|
||||
_destroySlider: function () {
|
||||
if (this.$('.js-slider').data('ui-slider')) {
|
||||
this.$('.js-slider').slider('destroy');
|
||||
}
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._destroySlider();
|
||||
CoreView.prototype.clean.call(this);
|
||||
}
|
||||
|
||||
});
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
var _ = require('underscore');
|
||||
var moment = require('moment');
|
||||
var ImportView = require('builder/components/modals/add-layer/content/imports/import-view');
|
||||
var UploadModel = require('builder/data/upload-model');
|
||||
var DatesRangePickerView = require('builder/components/date-picker-range/date-picker-range-view');
|
||||
var TwitterCategoriesView = require('./twitter-categories/twitter-categories-view');
|
||||
var CreditsUsageView = require('./credits-usage-view.js');
|
||||
var template = require('./import-twitter.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'configModel',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Import twitter panel
|
||||
*
|
||||
* - It accepts up to 3 categories
|
||||
* - Date range can't be longer than 30 days
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = ImportView.extend({
|
||||
options: {
|
||||
acceptSync: false,
|
||||
type: 'service',
|
||||
service: 'twitter_search'
|
||||
},
|
||||
|
||||
className: 'ImportPanel ImportTwitterPanel',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this.model = new UploadModel({
|
||||
type: this.options.type,
|
||||
service_name: this.options.service
|
||||
}, {
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_disabledDueLackOfCredentials: function () {
|
||||
return !this._userModel.hasOwnTwitterCredentials();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(
|
||||
template({
|
||||
currentGMT: moment().format('Z')
|
||||
})
|
||||
);
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
// Categories
|
||||
var categories = this.categories = new TwitterCategoriesView({
|
||||
disabled: this._disabledDueLackOfCredentials()
|
||||
});
|
||||
categories.bind('changeCategory', this._setModel, this);
|
||||
this.$('.ImportTwitterPanel-categories').append(categories.render().el);
|
||||
this.addView(categories);
|
||||
|
||||
// Date picker
|
||||
var datepicker = this.datepicker = new DatesRangePickerView({
|
||||
className: 'DatePicker DatePicker--withBorder',
|
||||
disabled: this._disabledDueLackOfCredentials()
|
||||
});
|
||||
datepicker.bind('changeDate', this._setModel, this);
|
||||
this.$('.js-picker').append(datepicker.render().el);
|
||||
this.addView(datepicker);
|
||||
|
||||
// Use slider
|
||||
var creditsUsage = this.creditsUsage = new CreditsUsageView({
|
||||
el: this.$('.CreditsUsage'),
|
||||
userModel: this._userModel,
|
||||
disabled: this._disabledDueLackOfCredentials()
|
||||
});
|
||||
creditsUsage.bind('maxCreditsChange', this._setModel, this);
|
||||
creditsUsage.render();
|
||||
this.addView(creditsUsage);
|
||||
|
||||
this._setModel();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this._triggerChange, this);
|
||||
},
|
||||
|
||||
_getCategories: function () {
|
||||
var categories = this.categories.getCategories();
|
||||
return _.filter(categories, function (c) {
|
||||
return c.category && c.terms.length > 0;
|
||||
});
|
||||
},
|
||||
|
||||
_getDates: function () {
|
||||
return this.datepicker.getDates();
|
||||
},
|
||||
|
||||
_getMaxCredits: function () {
|
||||
return this.creditsUsage.getMaxCredits();
|
||||
},
|
||||
|
||||
_setModel: function () {
|
||||
var categories = this._getCategories();
|
||||
var dates = this._getDates();
|
||||
var maxCredits = this._getMaxCredits();
|
||||
var d = {
|
||||
categories: categories,
|
||||
dates: dates
|
||||
};
|
||||
|
||||
this.model.setUpload({
|
||||
value: d,
|
||||
service_item_id: d,
|
||||
user_defined_limits: {
|
||||
twitter_credits_limit: maxCredits
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<div class="ImportPanel-header">
|
||||
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
|
||||
<%- _t('components.modals.add-layer.imports.twitter.title') %>
|
||||
</h3>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<%- _t('components.modals.add-layer.imports.twitter.terms-desc') %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="ImportPanel-body">
|
||||
<form class="Form">
|
||||
<div class="ImportTwitterPanel-categories"></div>
|
||||
<div class="ImportTwitterPanel-datePicker">
|
||||
<div class="Form-row">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium js-category"><%- _t('components.modals.add-layer.imports.twitter.from-to') %></label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer js-picker CDB-Text CDB-Size-medium"></div>
|
||||
<div class="Form-rowInfo DatePicker-info">
|
||||
<p class="CDB-Text CDB-Size-small u-altTextColor"><%- _t('components.modals.add-layer.imports.twitter.twitter-gmt') %> <br/>(<%- _t('components.modals.add-layer.imports.twitter.your-gmt') %><%- currentGMT %>)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ImportTwitterPanel-creditsUsage">
|
||||
<div class="Form-row">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium js-category"><%- _t('components.modals.add-layer.imports.twitter.use') %></label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer CreditsUsage">
|
||||
<div class="UISlider CreditsUsage-slider js-slider"></div>
|
||||
<div class="CreditsUsage-info CDB-Text CDB-Size-medium js-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
var Backbone = require('backbone');
|
||||
var TwitterCategoryModel = require('./twitter-category-model');
|
||||
|
||||
// Twitter categories collection
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
model: TwitterCategoryModel
|
||||
});
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var TwitterCategoriesCollection = require('./twitter-categories-collection');
|
||||
var TwitterCategoryModel = require('./twitter-category-model');
|
||||
var TwitterCategoryView = require('./twitter-category-view');
|
||||
|
||||
/**
|
||||
* Twitter category list view
|
||||
* - It will generate a collection to store all the
|
||||
* terms added.
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
_MAX_CATEGORIES: 4,
|
||||
_MAX_TERMS: 29,
|
||||
|
||||
initialize: function (opts) {
|
||||
this._disabled = (opts && opts.disabled) ? opts.disabled : false;
|
||||
|
||||
// Add a first empty model
|
||||
var m = this._generateCategory();
|
||||
this.collection = new TwitterCategoriesCollection([ m ]);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.collection.each(this._addCategory, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.collection.bind('change', this._manageCategories, this);
|
||||
this.collection.bind('change', this._onCategoryChange, this);
|
||||
this.add_related_model(this.collection);
|
||||
},
|
||||
|
||||
_manageCategories: function () {
|
||||
var collection_size = this.collection.size();
|
||||
|
||||
// Check if already created models are completed
|
||||
var nonFilled = this.collection.filter(function (m) {
|
||||
return m.get('terms').length === 0;
|
||||
});
|
||||
|
||||
// if so, generate new one
|
||||
if (nonFilled.length === 0 && collection_size < this._MAX_CATEGORIES) {
|
||||
var categoryModel = this._generateCategory();
|
||||
this.collection.add(categoryModel);
|
||||
this._addCategory(categoryModel);
|
||||
return false;
|
||||
}
|
||||
|
||||
// else, let's check
|
||||
if (nonFilled.length > 0) {
|
||||
var mdl = _.first(nonFilled);
|
||||
var view = _.find(this._subviews, function (view) {
|
||||
return mdl.cid === view.model.cid;
|
||||
});
|
||||
var pos = view.$el.index();
|
||||
|
||||
// Only one item in the collection, do nothing
|
||||
if (collection_size === 1) return false;
|
||||
|
||||
// If it is the last item but there is no more items, do nothing
|
||||
if (pos === (collection_size - 1)) return false;
|
||||
|
||||
// If it is not the last item and there is another non-filled element
|
||||
// let's remove that one.
|
||||
if (pos !== (collection_size - 1) && nonFilled.length > 1) {
|
||||
mdl = nonFilled[1];
|
||||
view = _.find(this._subviews, function (view) {
|
||||
return mdl.cid === view.model.cid;
|
||||
});
|
||||
this._removeCategory(view);
|
||||
}
|
||||
|
||||
// Reorder category indexes :(
|
||||
this._sortCategoryIndex();
|
||||
}
|
||||
},
|
||||
|
||||
// Set proper index after any category removed
|
||||
_sortCategoryIndex: function () {
|
||||
var self = this;
|
||||
|
||||
// Hack to set properly category numbers
|
||||
this.$('.twitter-category').each(function (i, el) {
|
||||
// Get category, removing Category word
|
||||
var category = $(el).find('.js-category').text().replace(_t('components.modals.add-layer.imports.twitter.category') + ' ', '');
|
||||
|
||||
if (category !== (i + 1)) {
|
||||
// Find model
|
||||
var m = self.collection.find(function (m) { return m.get('category') === category; });
|
||||
// Find view
|
||||
m.set('category', (i + 1).toString());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_generateCategory: function () {
|
||||
return new TwitterCategoryModel({
|
||||
disabled: this._disabled,
|
||||
terms: [],
|
||||
category: (this.collection ? (this.collection.size() + 1) : 1).toString()
|
||||
});
|
||||
},
|
||||
|
||||
_addCategory: function (m) {
|
||||
var category = new TwitterCategoryView({ model: m });
|
||||
|
||||
category.bind('submit', this._onCategorySubmit, this);
|
||||
category.bind('limit', this._onCategoryLimit, this);
|
||||
category.bind('nolimit', this._onCategoryNoLimit, this);
|
||||
|
||||
this.$el.append(category.render().el);
|
||||
|
||||
this.addView(category);
|
||||
this.trigger('addCategory');
|
||||
},
|
||||
|
||||
_removeCategory: function (v) {
|
||||
v.hide();
|
||||
v.clean();
|
||||
v.model.destroy();
|
||||
this.trigger('removeCategory');
|
||||
},
|
||||
|
||||
_onCategorySubmit: function () {
|
||||
this.trigger('submitCategory', this.collection.toJSON(), this);
|
||||
},
|
||||
|
||||
_onCategoryLimit: function () {
|
||||
this.trigger('limitCategory', this.collection.toJSON(), this);
|
||||
},
|
||||
|
||||
_onCategoryNoLimit: function () {
|
||||
this.trigger('noLimitCategory', this.collection.toJSON(), this);
|
||||
},
|
||||
|
||||
_onCategoryChange: function () {
|
||||
this.trigger('changeCategory', this.collection.toJSON(), this);
|
||||
},
|
||||
|
||||
getCategories: function () {
|
||||
return this.collection.toJSON();
|
||||
}
|
||||
|
||||
});
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var TERMS_CHARS = 4;
|
||||
|
||||
// Twitter category model
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
_MAX_COUNTER: 1014,
|
||||
|
||||
_CHAR_MAP: {
|
||||
' ': 2,
|
||||
'-': 2,
|
||||
'_': 2,
|
||||
'.': 2
|
||||
},
|
||||
|
||||
defaults: {
|
||||
terms: [],
|
||||
category: '',
|
||||
counter: 1014
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.bind('change:terms', this._setCounter, this);
|
||||
},
|
||||
|
||||
_setCounter: function () {
|
||||
var count = this._MAX_COUNTER;
|
||||
var self = this;
|
||||
|
||||
// Check terms number
|
||||
if (this.get('terms').length > 1) {
|
||||
count = count - ((this.get('terms').length - 1) * TERMS_CHARS);
|
||||
}
|
||||
|
||||
// Count characters
|
||||
_.each(this.get('terms'), function (term) {
|
||||
_.each(term, function (c) {
|
||||
if (self._CHAR_MAP[c] !== undefined) {
|
||||
count = count - self._CHAR_MAP[c];
|
||||
} else {
|
||||
count--;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Count never should be less than 0 please!
|
||||
this.set('counter', Math.max(0, count));
|
||||
}
|
||||
|
||||
});
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var template = require('./twitter-category.tpl');
|
||||
|
||||
/**
|
||||
* Twitter category item view
|
||||
* - It just needs a twitter category model
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'TwitterCategory',
|
||||
|
||||
_MAX_CATEGORIES: 4,
|
||||
_MAX_TERMS: 29,
|
||||
|
||||
events: {
|
||||
'keydown .js-terms': '_onInputChange',
|
||||
'keypress .js-terms': '_onInputChange',
|
||||
'keyup .js-terms': '_onInputChange'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.empty();
|
||||
this.$el.append(
|
||||
template({
|
||||
disabled: this.model.get('disabled'),
|
||||
terms: this.model.get('terms'),
|
||||
category: this.model.get('category'),
|
||||
counter: this.model.get('counter')
|
||||
})
|
||||
);
|
||||
|
||||
this.show();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
_.bindAll(this, '_onInputChange');
|
||||
this.model.bind('change:category', this._onCategoryChange, this);
|
||||
},
|
||||
|
||||
_onCategoryChange: function () {
|
||||
this.$('.js-category').text(_t('components.modals.add-layer.imports.twitter.category') + ' ' + this.model.get('category'));
|
||||
},
|
||||
|
||||
_onInputChange: function (e) {
|
||||
var value = $(e.target).val();
|
||||
var d = {};
|
||||
|
||||
if (e.keyCode === 13/* ENTER */) {
|
||||
e.preventDefault();
|
||||
this.trigger('submit', this.model, this);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.$('.CDB-IconFont-twitter').toggleClass('is-highlighted', value.length > 0);
|
||||
|
||||
// Check if it is possible to add new characters
|
||||
// if not, stop the action, unless user is deleting
|
||||
// any previous character
|
||||
if ((this.model.get('counter') === 0 || this.model.get('terms').length > this._MAX_TERMS) &&
|
||||
e.keyCode !== 37 /* left */ && e.keyCode !== 39 /* right */ && e.keyCode !== 8 && value.length > 0) {
|
||||
this.killEvent(e);
|
||||
this.trigger('limit', this.model, this);
|
||||
return false;
|
||||
} else {
|
||||
this.trigger('nolimit', this.model, this);
|
||||
}
|
||||
|
||||
// Get valid terms array
|
||||
if (!value) {
|
||||
value = [];
|
||||
} else {
|
||||
value = value.split(',');
|
||||
}
|
||||
|
||||
d['terms'] = value;
|
||||
|
||||
this.model.set(d);
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.$el.addClass('enabled');
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.$el.removeClass('enabled');
|
||||
}
|
||||
|
||||
});
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<div class="Form-row">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium js-category"><%- _t('components.modals.add-layer.imports.twitter.category') %> <%- category %></label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<input type="text" <% if (disabled) { %>disabled="disabled"<% } %> class="CDB-Text CDB-Size-medium Form-input Form-input--longer has-icon js-terms <%- disabled ? 'is-disabled' : '' %>" value="<%- terms.join(",") %>" placeholder="<%- _t('components.modals.add-layer.imports.twitter.terms-placeholder') %>" />
|
||||
<i class="CDB-IconFont CDB-IconFont-twitter Form-inputIcon"></i>
|
||||
</div>
|
||||
</div>
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var UploadModel = require('builder/data/upload-model');
|
||||
|
||||
/**
|
||||
* Default view for an import item
|
||||
*
|
||||
* - It is based in an upload model.
|
||||
* - Will trigger a change when model changes.
|
||||
* - It returns their data if it is requested with a method.
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.userModel) throw new Error('userModel is required');
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
|
||||
this._configModel = opts.configModel;
|
||||
this._userModel = opts.userModel;
|
||||
this.model = new UploadModel(null, {
|
||||
configModel: this._configModel,
|
||||
userModel: this._userModel
|
||||
});
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this._triggerChange, this);
|
||||
},
|
||||
|
||||
_triggerChange: function () {
|
||||
this.trigger('change', this.model.toJSON(), this);
|
||||
},
|
||||
|
||||
getModelData: function () {
|
||||
if (this.model) {
|
||||
return this.model.toJSON();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
});
|
||||
Executable
+338
@@ -0,0 +1,338 @@
|
||||
var cdb = require('internal-carto.js');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var ViewFactory = require('builder/components/view-factory');
|
||||
var importViewTemplate = require('./imports.tpl');
|
||||
var importTabViewTemplate = require('./import-tab.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var TabPaneView = require('builder/components/tab-pane/tab-pane-view');
|
||||
var TabPaneCollection = require('builder/components/tab-pane/tab-pane-collection');
|
||||
var _ = require('underscore');
|
||||
var IMPORT_OPTIONS = require('./import-options');
|
||||
var TABS_PER_ROW = 5;
|
||||
var DEFAULT_IMPORT = 'file';
|
||||
var ROW_WIDTH = 800;
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'createModel',
|
||||
'userModel',
|
||||
'configModel',
|
||||
'privacyModel',
|
||||
'guessingModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Imports view
|
||||
*
|
||||
* Displays all the import options available
|
||||
* through new create dialog.
|
||||
*
|
||||
* IMPORTANT!!
|
||||
*
|
||||
* If you need to add a new import pane:
|
||||
*
|
||||
* - Create the proper class within imports folder and its tests.
|
||||
* - Add necessary info in import_options file.
|
||||
* - Create a check function here if needed, if not will appear
|
||||
* always enabled (for everybody!).
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'ImportOptions',
|
||||
|
||||
events: {
|
||||
'click .js-goNext': '_moveToNextTabs',
|
||||
'click .js-goPrev': '_moveToPrevTabs'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initModels();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this._destroyBinds();
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
this._generateContent();
|
||||
this._generateNavigationTabs();
|
||||
this._initBinds();
|
||||
this._setOption();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this.model = new Backbone.Model({
|
||||
page: 1,
|
||||
maxPages: 0
|
||||
});
|
||||
},
|
||||
|
||||
// //////////////////
|
||||
// TABS && PANES! //
|
||||
// //////////////////
|
||||
|
||||
_generateContent: function () {
|
||||
var paneItems = [];
|
||||
|
||||
_.each(IMPORT_OPTIONS, function (importConfig, i) {
|
||||
var obj = {};
|
||||
if (!_.isEmpty(importConfig) && importConfig.enabled(this._configModel, this._userModel)) {
|
||||
obj = {
|
||||
name: importConfig.name,
|
||||
selected: importConfig.name === 'file'
|
||||
};
|
||||
|
||||
obj.createButtonView = function () {
|
||||
return ViewFactory.createByTemplate(
|
||||
importTabViewTemplate,
|
||||
{
|
||||
title: cdb.core.sanitize.html(importConfig.title || importConfig.name),
|
||||
name: importConfig.name
|
||||
},
|
||||
{
|
||||
tagName: 'button',
|
||||
className: 'TabLink ' + 'js-' + importConfig.name + 'Tab'
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var pane;
|
||||
|
||||
// Check if import option function exists
|
||||
var fn = this['_check' + i + 'Import'];
|
||||
var isEnabled;
|
||||
|
||||
if (fn) {
|
||||
isEnabled = fn.bind(this)();
|
||||
}
|
||||
|
||||
if ((isEnabled || isEnabled === undefined) && !_.isEmpty(importConfig)) {
|
||||
var ImportView = importConfig.view;
|
||||
pane = new ImportView(
|
||||
_.extend(
|
||||
importConfig.options || {},
|
||||
{
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel,
|
||||
title: importConfig.title,
|
||||
createModel: this._createModel,
|
||||
privacyModel: this._privacyModel,
|
||||
guessingModel: this._guessingModel
|
||||
}
|
||||
)
|
||||
);
|
||||
} else if (importConfig.fallback) {
|
||||
pane = ViewFactory.createByTemplate(importConfig.fallback);
|
||||
}
|
||||
|
||||
if (pane) {
|
||||
pane.render();
|
||||
pane.bind('change', this._setUploadModel, this);
|
||||
obj.createContentView = function () {
|
||||
return pane;
|
||||
};
|
||||
}
|
||||
|
||||
paneItems.push(obj);
|
||||
}
|
||||
}, this);
|
||||
|
||||
this._tabPaneCollection = new TabPaneCollection(paneItems);
|
||||
this._tabPaneView = new TabPaneView({
|
||||
template: importViewTemplate,
|
||||
collection: this._tabPaneCollection,
|
||||
tabPaneItemOptions: {
|
||||
tagName: 'li',
|
||||
klassName: 'ImportOptions-tab'
|
||||
}
|
||||
});
|
||||
this.$el.append(this._tabPaneView.render().el);
|
||||
},
|
||||
|
||||
_generateNavigationTabs: function () {
|
||||
var numTabs = this.$('.ImportOptions-tab').size();
|
||||
if (numTabs <= 1) {
|
||||
this.$('.AddLayer-tabs').hide();
|
||||
}
|
||||
|
||||
this.model.set('maxPages', Math.ceil(numTabs / TABS_PER_ROW));
|
||||
this._checkNavigationTabs();
|
||||
if (this.model.get('maxPages') <= 1) {
|
||||
this.$('.ImportOptions-navigation').hide();
|
||||
}
|
||||
},
|
||||
|
||||
_moveToNextTabs: function () {
|
||||
var page = this.model.get('page');
|
||||
var maxPages = this.model.get('maxPages');
|
||||
|
||||
if (page < maxPages) {
|
||||
this.model.set('page', page + 1);
|
||||
}
|
||||
},
|
||||
|
||||
_moveToPrevTabs: function () {
|
||||
var page = this.model.get('page');
|
||||
if (page > 1) {
|
||||
this.model.set('page', page - 1);
|
||||
}
|
||||
},
|
||||
|
||||
_moveNavigationTabs: function () {
|
||||
var page = this.model.get('page');
|
||||
var rowWidth = ROW_WIDTH;
|
||||
|
||||
this.$('.js-menu').css('margin-left', '-' + (rowWidth * (page - 1)) + 'px');
|
||||
this._checkNavigationTabs();
|
||||
},
|
||||
|
||||
_checkNavigationTabs: function () {
|
||||
var page = this.model.get('page');
|
||||
var maxPages = this.model.get('maxPages');
|
||||
this.$('.js-goPrev').toggleClass('is-disabled', page < 2);
|
||||
this.$('.js-goNext').toggleClass('is-disabled', page >= maxPages);
|
||||
},
|
||||
|
||||
_checkGDriveImport: function () {
|
||||
if (!this._configModel.get('oauth_gdrive')) {
|
||||
this._setFailedTab('gdrive', 'key');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_checkDropboxImport: function () {
|
||||
if (!this._configModel.get('oauth_dropbox')) {
|
||||
this._setFailedTab('dropbox', 'key');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_checkBoxImport: function () {
|
||||
if (!this._configModel.get('oauth_box')) {
|
||||
this._setFailedTab('box', 'key');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_checkTwitterImport: function () {
|
||||
if (!this._configModel.get('datasource_search_twitter')) {
|
||||
this._setFailedTab('twitter', 'key');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._userModel.get('twitter').enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._userModel.canCreateTwitterDataset()) {
|
||||
this._setFailedTab('twitter', 'credits');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_checkInstagramImport: function () {
|
||||
if (!this._userModel.featureEnabled('instagram_import')) {
|
||||
return false;
|
||||
}
|
||||
if (!this._configModel.get('oauth_instagram')) {
|
||||
this._setFailedTab('instagram', 'key');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_checkSalesforceImport: function () {
|
||||
// Connector is now disabled for everyone.
|
||||
// You might be able to use the following check to determine
|
||||
// if user/org has this connector enabled:
|
||||
// this._userModel.get('salesforce').enabled
|
||||
return false;
|
||||
},
|
||||
|
||||
_checkMailchimpImport: function () {
|
||||
if (!this._configModel.get('oauth_mailchimp')) {
|
||||
this._setFailedTab('mailchimp', 'key');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._userModel.featureEnabled('mailchimp_import')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_setFailedTab: function (tabName, type) {
|
||||
var $tab = this.$('.js-' + tabName + 'Tab');
|
||||
$tab.addClass('disabled');
|
||||
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: $tab,
|
||||
title: function () {
|
||||
return _t('components.modals.add-layer.imports.tab-options-error.' + type, { name: tabName });
|
||||
}
|
||||
});
|
||||
this.addView(tooltip);
|
||||
},
|
||||
|
||||
_setUploadModel: function (d) {
|
||||
var uploadModel = this._createModel.getUploadModel();
|
||||
uploadModel.setFresh(d);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:page', this._moveNavigationTabs, this);
|
||||
if (this._tabPaneCollection) {
|
||||
this._tabPaneCollection.bind('change:selected', this._onTabChange, this);
|
||||
}
|
||||
},
|
||||
|
||||
_destroyBinds: function () {
|
||||
if (this._tabPaneCollection) {
|
||||
this._tabPaneCollection.bind('change:selected', null, this);
|
||||
}
|
||||
},
|
||||
|
||||
_setOption: function () {
|
||||
// First option > data file
|
||||
var tabPaneModel = _.first(this._tabPaneCollection.where({ name: DEFAULT_IMPORT }));
|
||||
if (tabPaneModel) {
|
||||
tabPaneModel.set('selected', true);
|
||||
this._updateImportOption();
|
||||
}
|
||||
},
|
||||
|
||||
_updateImportOption: function () {
|
||||
this._createModel.setActiveImportPane(this._tabPaneView.getSelectedTabPaneName());
|
||||
},
|
||||
|
||||
// //////////
|
||||
// Events //
|
||||
// //////////
|
||||
|
||||
_onTabChange: function (tabModel) {
|
||||
// When the tab changes we lose all event listeners
|
||||
this._tabPaneCollection.each(function (model) {
|
||||
if (!model.get('selected')) return;
|
||||
const pane = model.attributes.createContentView();
|
||||
pane.unbind('change', this._setUploadModel, this);
|
||||
pane.bind('change', this._setUploadModel, this);
|
||||
}, this);
|
||||
this._updateImportOption();
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._destroyBinds();
|
||||
CoreView.prototype.clean.call(this);
|
||||
}
|
||||
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<div class="ImportOptions-navigation">
|
||||
<button class="Carousel-Navigation CDB-Size-large ImportOptions-navigationButton ImportOptions-navigationButton--prev js-goPrev">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev u-actionTextColor"></i>
|
||||
</button>
|
||||
<button class="Carousel-Navigation CDB-Size-large ImportOptions-navigationButton ImportOptions-navigationButton--next js-goNext">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowNext u-actionTextColor"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="AddLayer-tabs">
|
||||
<ul class="ImportOptions-tabsList js-menu CDB-Text CDB-Size-medium u-altTextColor"></ul>
|
||||
<!--
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor u-tSpace-xl">
|
||||
<%- _t('components.modals.add-layer.imports.feedback.text') %>
|
||||
<a href="<%- _t('components.modals.add-layer.imports.feedback.url') %>" target="_blank">
|
||||
<%- _t('components.modals.add-layer.imports.feedback.button') %>
|
||||
</a>
|
||||
</p>
|
||||
-->
|
||||
|
||||
</div>
|
||||
<div class="ImportOptions-panes js-content"></div>
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var DatasetsView = require('./datasets/datasets-view');
|
||||
var ImportsView = require('./imports/imports-view');
|
||||
var TabPaneView = require('builder/components/tab-pane/tab-pane-view');
|
||||
var TabPaneCollection = require('builder/components/tab-pane/tab-pane-collection');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'createModel',
|
||||
'userModel',
|
||||
'configModel',
|
||||
'privacyModel',
|
||||
'guessingModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Create listing view
|
||||
*
|
||||
* It will display all the possibilities to select
|
||||
* any of your current datasets or connect a new dataset.
|
||||
*
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'CreateDialog-listing CreateDialog-listing--noPaddingTop',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
this._onChangeListing();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
// Bug with binding... do not work with the usual one for some reason :(
|
||||
this.listenTo(this._createModel, 'change:listing', this._onChangeListing);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var self = this;
|
||||
var paneModels = [];
|
||||
|
||||
paneModels.push({
|
||||
name: 'datasets',
|
||||
selected: false,
|
||||
createContentView: function () {
|
||||
return new DatasetsView({
|
||||
userModel: self._userModel,
|
||||
createModel: self._createModel
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (this._userModel.canCreateDatasets()) {
|
||||
paneModels.push({
|
||||
name: 'import',
|
||||
selected: false,
|
||||
createContentView: function () {
|
||||
return new ImportsView({
|
||||
userModel: self._userModel,
|
||||
configModel: self._configModel,
|
||||
createModel: self._createModel,
|
||||
privacyModel: self._privacyModel,
|
||||
guessingModel: self._guessingModel
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._tabPaneCollection = new TabPaneCollection(paneModels);
|
||||
var paneTabPaneView = new TabPaneView({
|
||||
collection: this._tabPaneCollection
|
||||
});
|
||||
this.addView(paneTabPaneView);
|
||||
this.$el.append(paneTabPaneView.render().el);
|
||||
},
|
||||
|
||||
_onChangeListing: function () {
|
||||
if (this._tabPaneCollection) {
|
||||
var context = this._createModel.get('listing');
|
||||
var paneModel = _.first(this._tabPaneCollection.where({ name: context }));
|
||||
paneModel.set('selected', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
var cdb = require('internal-carto.js');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var template = require('./navigation.tpl');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'configModel',
|
||||
'createModel',
|
||||
'userModel',
|
||||
'routerModel',
|
||||
'tablesCollection'
|
||||
];
|
||||
|
||||
/**
|
||||
* Listing datasets navigation.
|
||||
*
|
||||
* - 'Filter by' datasets.
|
||||
* - 'Search' any pattern within dataset collection.
|
||||
*
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'submit .js-search-form': '_submitSearch',
|
||||
'keydown .js-search-form': '_onSearchKeyDown',
|
||||
'click .js-search-form': 'killEvent',
|
||||
'click .js-search-link': '_onSearchClick',
|
||||
'click .js-clean-search': '_onCleanSearchClick',
|
||||
'click .js-shared': '_onSharedClick',
|
||||
'click .js-library': '_onLibraryClick',
|
||||
'click .js-connect': '_onConnectClick',
|
||||
'click .js-datasets': '_onDatasetsClick',
|
||||
'click .js-create-empty': '_onCreateEmptyClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
this.model = new Backbone.Model();
|
||||
|
||||
this._preRender();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
// It is necessary to add two static elements because
|
||||
// they can't be removed/replaced using render method
|
||||
// each time a change (in a model or a collection) happens.
|
||||
// This is due to the behaviour of the CSS animations.
|
||||
_preRender: function () {
|
||||
var $uInner = $('<div>').addClass('u-inner');
|
||||
var $filtersInner = $('<div>').addClass('Filters-inner');
|
||||
this.$el.append($uInner.append($filtersInner));
|
||||
},
|
||||
|
||||
render: function (m, c) {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$('.Filters-inner').html(
|
||||
template(
|
||||
_.extend(
|
||||
{
|
||||
showDatasets: this._createModel.showDatasets(),
|
||||
createModelType: this._createModel.get('type'),
|
||||
canCreateDataset: this._userModel.canCreateDatasets(),
|
||||
listingType: this._createModel.get('listing'),
|
||||
isInsideOrg: this._userModel.isInsideOrg(),
|
||||
totalShared: this._tablesCollection.getTotalStat('total_shared'),
|
||||
selectedItemsCount: this._selectedItems().length,
|
||||
hasDataLibrary: this._configModel.dataLibraryEnabled()
|
||||
},
|
||||
this._routerModel.attributes
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
this._animate();
|
||||
if (this._routerModel.isSearching()) {
|
||||
this._focusSearchInput();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:isSearchEnabled', this._onChangeIsSearchEnabled, this);
|
||||
this.listenTo(this._createModel, 'change:listing', this.render, this);
|
||||
this.listenTo(this._routerModel, 'change', this.render, this);
|
||||
this.listenTo(this._tablesCollection, 'sync', this.render, this);
|
||||
},
|
||||
|
||||
_onChangeIsSearchEnabled: function (model, isSearchEnabled) {
|
||||
this._enableSearchUI(isSearchEnabled);
|
||||
|
||||
if (this._routerModel.isSearching()) {
|
||||
this._cleanSearch();
|
||||
} else if (isSearchEnabled) {
|
||||
this._$searchInput().val('');
|
||||
this._focusSearchInput();
|
||||
}
|
||||
},
|
||||
|
||||
_$searchInput: function () {
|
||||
return this.$('.js-search-input');
|
||||
},
|
||||
|
||||
_focusSearchInput: function () {
|
||||
this._$searchInput()
|
||||
.select()
|
||||
.focus();
|
||||
},
|
||||
|
||||
_onSearchKeyDown: function (e) {
|
||||
if (e.keyCode === 27) { // ESC
|
||||
this.killEvent(e);
|
||||
this._cleanSearch();
|
||||
}
|
||||
},
|
||||
|
||||
_selectedItems: function () {
|
||||
return this._tablesCollection.where({ selected: true });
|
||||
},
|
||||
|
||||
_animate: function () {
|
||||
this._enableSearchUI(!!this._routerModel.isSearching());
|
||||
|
||||
// Check if user doesn't have any table and it is in library section
|
||||
// to remove useless shadow
|
||||
var inLibrarySection = this._routerModel.get('library');
|
||||
var inDatasetsSection = this._createModel.get('listing') === 'datasets';
|
||||
var hasDatasets = this._tablesCollection.getTotalStat('total_user_entries') > 0;
|
||||
this.$el.toggleClass('no-shadow', inLibrarySection && !hasDatasets && inDatasetsSection);
|
||||
},
|
||||
|
||||
_enableSearchUI: function (enable) {
|
||||
this.$('.js-search-field').toggle(enable);
|
||||
this.$('.js-links-list').toggleClass('is-hidden', enable);
|
||||
this.$('.js-order-list').toggleClass('is-hidden', enable);
|
||||
},
|
||||
|
||||
_onDatasetsClick: function () {
|
||||
this._routerModel.set({
|
||||
shared: 'no',
|
||||
library: false,
|
||||
page: 1
|
||||
});
|
||||
this._routerModel.trigger('change', this._routerModel);
|
||||
this._createModel.set('listing', 'datasets');
|
||||
},
|
||||
|
||||
_onSharedClick: function () {
|
||||
this._routerModel.set({
|
||||
shared: 'only',
|
||||
library: false,
|
||||
page: 1
|
||||
});
|
||||
this._routerModel.trigger('change', this._routerModel);
|
||||
this._createModel.set('listing', 'datasets');
|
||||
},
|
||||
|
||||
_onLibraryClick: function () {
|
||||
this._routerModel.set({
|
||||
shared: 'no',
|
||||
library: true,
|
||||
page: 1
|
||||
});
|
||||
this._routerModel.trigger('change', this._routerModel);
|
||||
this._createModel.set('listing', 'datasets');
|
||||
},
|
||||
|
||||
_onConnectClick: function () {
|
||||
if (this._userModel.canCreateDatasets()) {
|
||||
this._createModel.set('listing', 'import');
|
||||
}
|
||||
},
|
||||
|
||||
_onCreateEmptyClick: function () {
|
||||
if (this._userModel.canCreateDatasets()) {
|
||||
this._createModel.createFromScratch();
|
||||
}
|
||||
},
|
||||
|
||||
// Selection actions
|
||||
_onSearchClick: function (e) {
|
||||
this.killEvent(e);
|
||||
this.model.set('isSearchEnabled', !this.model.get('isSearchEnabled'));
|
||||
},
|
||||
|
||||
// Filter actions
|
||||
_onCleanSearchClick: function (e) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this._cleanSearch();
|
||||
},
|
||||
|
||||
_cleanSearch: function () {
|
||||
this._routerModel.set({
|
||||
q: '',
|
||||
tag: '',
|
||||
shared: 'no',
|
||||
library: this._createModel.showLibrary()
|
||||
});
|
||||
this.model.set('isSearchEnabled', false);
|
||||
},
|
||||
|
||||
_submitSearch: function (e) {
|
||||
this.killEvent(e);
|
||||
var val = cdb.core.sanitize.html(this.$('.js-search-input').val().trim());
|
||||
var tag = val.search(':') === 0 ? val.replace(':', '') : '';
|
||||
var q = val.search(':') !== 0 ? val : '';
|
||||
|
||||
this._routerModel.set({
|
||||
page: 1,
|
||||
tag: tag,
|
||||
q: q,
|
||||
shared: 'yes'
|
||||
});
|
||||
|
||||
this._createModel.set('listing', 'datasets');
|
||||
}
|
||||
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<span class="Filters-separator"></span>
|
||||
|
||||
<div class="Filters-row">
|
||||
<div class="Filters-group">
|
||||
<div class="Filters-typeItem Filters-typeItem--searchEnabler js-search-enabler">
|
||||
<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>
|
||||
|
||||
<ul class="Filters-group js-links-list">
|
||||
<li class="Filters-typeItem js-filter-type">
|
||||
<button class="Filters-typeLink js-typeItem u-actionTextColor CDB-Text is-semibold u-upperCase CDB-Size-medium js-connect <%- listingType === 'import' ? 'is-selected' : '' %> <%- !canCreateDataset ? 'is-disabled' : '' %>">
|
||||
<%- _t('components.modals.add-layer.navigation.connect-dataset') %>
|
||||
</button>
|
||||
</li>
|
||||
<% if (showDatasets) { %>
|
||||
<li class="Filters-typeItem js-filter-type">
|
||||
<button class="Filters-typeLink js-typeItem u-actionTextColor CDB-Text is-semibold u-upperCase CDB-Size-medium js-datasets <%- listingType === 'datasets' && shared !== 'only' && !library ? 'is-selected' : '' %>">
|
||||
<%- _t('components.modals.add-layer.navigation.your-datasets') %>
|
||||
</button>
|
||||
</li>
|
||||
<% if (isInsideOrg) { %>
|
||||
<li class="Filters-typeItem js-filter-type">
|
||||
<button class="Filters-typeLink js-typeItem u-actionTextColor CDB-Text is-semibold u-upperCase CDB-Size-medium js-shared <%- listingType === 'datasets' && shared === "only" ? 'is-selected' : '' %>">
|
||||
<% if (totalShared) { %>
|
||||
<strong><%- totalShared %></strong>
|
||||
<% } %>
|
||||
<%- _t('components.modals.add-layer.navigation.shared-with-you') %>
|
||||
</button>
|
||||
</li>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% if (hasDataLibrary) { %>
|
||||
<li class="Filters-typeItem js-filter-type">
|
||||
<button class="Filters-typeLink js-typeItem u-actionTextColor CDB-Text is-semibold u-upperCase CDB-Size-medium js-library <%- listingType === 'datasets' && library ? 'is-selected' : '' %>">
|
||||
<%- _t('components.modals.add-layer.navigation.data-library') %>
|
||||
</button>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="Filters-typeItem Filters-typeItem--searchField js-search-field">
|
||||
<form class="Filters-searchForm js-search-form">
|
||||
<input class="Filters-searchInput CDB-Text CDB-Size-medium js-search-input" type="text" value="<%- ( tag && (':' + tag) ) || q %>" placeholder="<%- _t('components.modals.add-layer.navigation.search-placeholder') %>" />
|
||||
<% if (tag || q) { %>
|
||||
<button type="button" class="Filters-cleanSearch js-clean-search u-actionTextColor">
|
||||
<i class="CDB-IconFont CDB-IconFont-close"></i>
|
||||
</button>
|
||||
<% } %>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="Filters-addLayer js-order-list">
|
||||
<button class="Filters-typeLink js-typeItem u-actionTextColor CDB-Text is-semibold u-upperCase CDB-Size-medium js-create-empty <%- listingType === 'scratch' ? 'is-selected' : '' %> <%- !canCreateDataset ? 'is-disabled' : '' %>">
|
||||
<%- _t('components.modals.add-layer.navigation.create-empty-' + createModelType) %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var GuessingTogglerView = require('./guessing-toggler-view');
|
||||
var PrivacyTogglerView = require('./privacy-toggler-view');
|
||||
var template = require('./footer.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'privacyModel',
|
||||
'guessingModel',
|
||||
'createModel',
|
||||
'userModel',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Footer view for the add layer modal.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-ok': '_finish'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
canFinish: this._createModel.canFinish(),
|
||||
listing: this._createModel.get('listing')
|
||||
})
|
||||
);
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var guessingTogglerView = new GuessingTogglerView({
|
||||
guessingModel: this._guessingModel,
|
||||
userModel: this._userModel,
|
||||
createModel: this._createModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this.$('.js-footer-info').append(guessingTogglerView.render().el);
|
||||
this.addView(guessingTogglerView);
|
||||
|
||||
var privacyTogglerView = new PrivacyTogglerView({
|
||||
privacyModel: this._privacyModel,
|
||||
userModel: this._userModel,
|
||||
createModel: this._createModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this.$('.js-footerActions').prepend(privacyTogglerView.render().el);
|
||||
this.addView(privacyTogglerView);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._createModel.getUploadModel(), 'change', this.render);
|
||||
this.listenTo(this._createModel.getSelectedDatasetsCollection(), 'all', this._update);
|
||||
this.listenTo(this._createModel, 'change', this._update);
|
||||
},
|
||||
|
||||
_update: function () {
|
||||
var contentPane = this._createModel.get('contentPane');
|
||||
var listing = this._createModel.get('listing');
|
||||
if (contentPane === 'listing' && listing !== 'scratch') {
|
||||
this.render().show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_finish: function (e) {
|
||||
this.killEvent(e);
|
||||
if (this._createModel.canFinish()) {
|
||||
// Set proper guessing values before starting the upload
|
||||
// if dialog is in import section
|
||||
if (this._createModel.get('listing') === 'import') {
|
||||
var uploadModel = this._createModel.getUploadModel();
|
||||
uploadModel.setPrivacy(this._privacyModel.get('privacy'));
|
||||
uploadModel.setGuessing(this._guessingModel.get('guessing'));
|
||||
}
|
||||
this._userModel.updateTableCount();
|
||||
this._createModel.finish();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<div class="CreateDialog-footerShadow"></div>
|
||||
<div class="CreateDialog-footerLine"></div>
|
||||
<div class="CreateDialog-footerInner u-flex u-justifySpace u-alignCenter">
|
||||
<div class="js-footer-info CreateDialog-footerInfo u-secondaryTextColor">
|
||||
</div>
|
||||
<div class="CreateDialog-footerActions js-footerActions">
|
||||
<button class="js-ok CDB-Button CDB-Button--primary <%- canFinish ? '' : 'is-disabled' %>">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase"><%- _t('components.modals.add-layer.footer.add-layer') %></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./guessing-toggler.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'createModel',
|
||||
'guessingModel',
|
||||
'configModel',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Manages if upcoming import should guess or not.
|
||||
* Expected to be rendered in the footer of a create dialog.
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-toggle': '_toggle'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var htmlStr = '';
|
||||
if (this._createModel.showGuessingToggler()) {
|
||||
var uploadModel = this._createModel.getUploadModel();
|
||||
htmlStr = template({
|
||||
isGuessingEnabled: this._guessingModel.get('guessing'),
|
||||
importState: this._createModel.getImportState(),
|
||||
isUploadValid: uploadModel.isValidToUpload(),
|
||||
customHosted: this._configModel.get('cartodb_com_hosted'),
|
||||
isTwitterDeprecatedForUser: !this._userModel.hasOwnTwitterCredentials()
|
||||
});
|
||||
}
|
||||
this.$el.html(htmlStr);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._createModel.bind('change', this.render, this);
|
||||
this._guessingModel.bind('change', this.render, this);
|
||||
this.add_related_model(this._createModel);
|
||||
},
|
||||
|
||||
_toggle: function () {
|
||||
var value = !this._guessingModel.get('guessing');
|
||||
this._guessingModel.set('guessing', value);
|
||||
}
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<% if (importState !== "twitter") { %>
|
||||
<div class="u-iBlock CDB-Text CDB-Size-medium u-rSpace--xl js-toggle">
|
||||
<input class="CDB-Checkbox" type="checkbox" <%- isGuessingEnabled ? 'checked' : '' %>>
|
||||
<span class="u-iBlock CDB-Checkbox-face"></span>
|
||||
<label class="u-iBlock u-lSpace"><%- _t('components.modals.add-layer.footer.guessing-desc') %></label>
|
||||
</div>
|
||||
<% } else if (isTwitterDeprecatedForUser) { %>
|
||||
<div class="CDB-Text CDB-Size-medium">
|
||||
<span class="Dialog-footerWarning">
|
||||
<%- _t('components.modals.add-layer.footer.deprecated-connector') %>
|
||||
</span>
|
||||
<%= _t('components.modals.add-layer.footer.twitter-contact-support') %>
|
||||
</div>
|
||||
<% } else if (!customHosted) { %>
|
||||
<span class="CDB-Text CDB-Size-medium">
|
||||
<%- _t('components.modals.add-layer.footer.twitter-how-to-historical') %>
|
||||
<a href="mailto:support@carto.com">
|
||||
<%- _t('components.modals.add-layer.footer.contact-team') %>
|
||||
</a>
|
||||
</span>
|
||||
<% } %>
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var $ = require('jquery');
|
||||
var template = require('./privacy-toggler.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var publicPrivacy = 'PUBLIC';
|
||||
var privatePrivacy = 'PRIVATE';
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'createModel',
|
||||
'privacyModel',
|
||||
'configModel',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Change the privacy of the new dataset.
|
||||
* - If the user can't change the privacy, it will refer to the upgrade page
|
||||
* unless app is the "open source" version
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
if (this._createModel.showPrivacyToggler()) {
|
||||
var canChangePrivacy = this._userModel.canCreatePrivateDatasets();
|
||||
var privacy = this._privacyModel.get('privacy');
|
||||
var isPublic = privacy === publicPrivacy;
|
||||
var nextPrivacy = isPublic ? privatePrivacy : publicPrivacy;
|
||||
var icon = isPublic ? 'unlock' : 'lock';
|
||||
var upgradeUrl = this._configModel.get('upgrade_url') || window.upgrade_url;
|
||||
var canUpgrade = !this._configModel.get('cartodb_com_hosted') && !canChangePrivacy && upgradeUrl;
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
privacy: privacy,
|
||||
isDisabled: !canChangePrivacy,
|
||||
canUpgrade: canUpgrade,
|
||||
nextPrivacy: nextPrivacy,
|
||||
upgradeUrl: upgradeUrl,
|
||||
icon: icon
|
||||
})
|
||||
);
|
||||
|
||||
this._initViews();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._privacyModel.bind('change:privacy', this.render, this);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
// Tooltip
|
||||
this.addView(
|
||||
new TipsyTooltipView({
|
||||
el: this.$('.js-toggler'),
|
||||
html: true,
|
||||
title: function () {
|
||||
return $(this).attr('data-title');
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_onClick: function () {
|
||||
if (this._userModel.canCreatePrivateDatasets()) {
|
||||
var privacy = this._privacyModel.get('privacy');
|
||||
this._privacyModel.set('privacy', privacy === publicPrivacy ? privatePrivacy : publicPrivacy);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<% if (canUpgrade) { %>
|
||||
<a href="<%- upgradeUrl %>" class="PrivacyToggler js-toggler PrivacyToggler--<%- privacy %> is-disabled"
|
||||
data-title="<%- _t('components.modals.add-layer.footer.privacy-change-not-allowed') %>">
|
||||
<i class="CDB-IconFont CDB-IconFont-<%- icon %>"></i>
|
||||
</a>
|
||||
<% } else {%>
|
||||
<button type="button" class="PrivacyToggler js-toggler PrivacyToggler--<%- privacy %> <%- isDisabled ? 'is-disabled' : '' %>"
|
||||
data-title="
|
||||
<% if (!isDisabled) { %>
|
||||
<%- _t('components.modals.add-layer.footer.privacy-change', { privacy: privacy.toLowerCase() }) %>.<br /><%- _t('components.modals.add-layer.footer.privacy-click', { nextPrivacy: nextPrivacy.toLowerCase() }) %>.
|
||||
<% } else { %>
|
||||
<%- _t('components.modals.add-layer.footer.privacy-change-banned') %>
|
||||
<% } %>
|
||||
">
|
||||
<i class="CDB-IconFont CDB-IconFont-<%- icon %>"></i>
|
||||
</button>
|
||||
<% } %>
|
||||
Reference in New Issue
Block a user