Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
var CoreView = require('backbone/core-view');
var template = require('./apply-button.tpl');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'onApplyClick',
'overlayModel'
];
module.exports = CoreView.extend({
events: {
'click .js-apply': '_onClick'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._initBinds();
},
render: function () {
this.$el.html(template({
disabled: this._isDisabled()
}));
return this;
},
_initBinds: function () {
this.listenTo(this._overlayModel, 'change:visible', this.render);
},
_isDisabled: function () {
return this._overlayModel.get('visible');
},
_onClick: function () {
if (this._isDisabled()) return;
this._onApplyClick();
}
});

View File

@@ -0,0 +1,3 @@
<button class="u-lSpace--xl CDB-Button CDB-Button--primary <%- disabled ? 'is-disabled' : '' %> js-apply">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small"><%- _t("editor.infowindow.apply") %></span>
</button>

View File

@@ -0,0 +1,186 @@
var CoreView = require('backbone/core-view');
var Notifier = require('builder/components/notifier/notifier');
var UploadConfig = require('builder/config/upload-config');
var ErrorDetailsView = require('./error-details-view');
var WarningsDetailsView = require('./warnings-details-view');
var TwitterImportDetailsDialog = require('./twitter-import-details-view');
/**
* Import item within background importer
*
*/
var DELEGATIONS = {
loading: require('./delegated-import-views/loading'),
error: require('./delegated-import-views/failed'),
warning: require('./delegated-import-views/warnings'),
success: require('./delegated-import-views/success')
};
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');
if (!opts.modals) throw new Error('modals is required');
if (!opts.importModel) throw new Error('importModel is required');
this._userModel = opts.userModel;
this._configModel = opts.configModel;
this._modals = opts.modals;
this._importModel = opts.importModel;
this._showSuccessDetailsButton = opts.showSuccessDetailsButton;
this._notification = Notifier.addNotification({});
this._initBinds();
},
_initBinds: function () {
this._importModel.on('change', this.updateNotification, this);
this._importModel.on('remove', this.clean, this);
this._notification.on('notification:close', this._closeHandler, this);
this._notification.on('notification:action', this._actionHandler, this);
this.add_related_model(this._importModel);
this.add_related_model(this._notification);
},
_closeHandler: function () {
this.trigger('remove', this._importModel, this);
this._importModel.pause();
this.clean();
},
_actionHandler: function (action) {
if (action === 'show_errors') {
this._showImportError();
} else if (action === 'show_stats') {
this._showImportStats();
} else if (action === 'show_table') {
this._showImportDataset();
} else if (action === 'show_warnings') {
this._showImportWarnings();
}
},
_getStatus: function () {
var status = 'loading';
var failed = this._importModel.hasFailed();
var completed = this._importModel.hasCompleted();
var warnings = this._importModel.getWarnings();
if (failed) {
status = 'error';
} else if (completed && !warnings) {
status = 'success';
} else if (completed && warnings) {
status = 'warning';
}
return status;
},
_getInfo: function () {
var upload = this._importModel.get('upload');
var imp = this._importModel.get('import');
var d = {
name: '',
state: this._importModel.get('state'),
progress: '',
service: '',
step: this._importModel.get('step'),
failed: this._importModel.hasFailed(),
completed: this._importModel.hasCompleted(),
warnings: this._importModel.getWarnings(),
showSuccessDetailsButton: this._showSuccessDetailsButton,
tables_created_count: imp.tables_created_count
};
// Name
if (upload.type) {
if (upload.type === 'file') {
if (upload.value.length > 1) {
d.name = upload.value.length + ' files';
} else {
d.name = upload.value.name;
}
}
if (upload.type === 'url' || upload.type === 'remote') {
d.name = upload.value;
}
if (upload.type === 'service') {
d.name = upload.value && upload.value.filename || '';
}
if (upload.service_name === 'twitter_search') {
d.name = 'Twitter import';
}
if (upload.type === 'sql') {
d.name = 'SQL';
}
if (upload.type === 'duplication') {
d.name = upload.table_name || upload.value;
}
} else {
d.name = imp.display_name || imp.item_queue_id || 'import';
}
// Service
d.service = upload.service_name;
// Progress
if (this._importModel.get('step') === 'upload') {
d.progress = this._importModel.get('upload').progress;
} else {
d.progress = Math.max(0, (UploadConfig.uploadStates.indexOf(d.state) / UploadConfig.uploadStates.length) * 100);
}
d.progress = d.progress.toFixed(0);
return d;
},
updateNotification: function () {
var status = this._getStatus();
var delegated = DELEGATIONS[status];
delegated.call(this, this._importModel, this._notification, status, this._getInfo(), this._showSuccessDetailsButton);
},
_showImportDataset: function () {
var dataset = this._importModel.get('import').table_name;
window.location = this._configModel.get('base_url') + '/dataset/' + dataset;
},
_showImportStats: function () {
var self = this;
this._modals.create(function (modalModel) {
return new TwitterImportDetailsDialog({
modalModel: modalModel,
userModel: self._userModel,
model: self._importModel
});
});
},
_showImportError: function () {
var self = this;
this._modals.create(function (modalModel) {
return new ErrorDetailsView({
configModel: self._configModel,
modalModel: modalModel,
error: self._importModel.getError(),
userModel: self._userModel
});
});
},
_showImportWarnings: function () {
var self = this;
this._modals.create(function (modalModel) {
return new WarningsDetailsView({
modalModel: modalModel,
warnings: self._importModel.getWarnings(),
userModel: self._userModel
});
});
}
});

View File

@@ -0,0 +1,9 @@
<% if (failed) { %>
<%- _t('components.background-importer.background-importer-item.error-connecting', { name: name }) %> <% if (service) { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %>
<% } else if (completed && !warnings) { %>
<%- name %> <% if (service && service != "twitter_search") { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %> <%- _t('components.background-importer.background-importer-item.completed') %>!
<% } else if (completed && warnings) { %>
Some warnings were produced for <%- name %> <% if (service) { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %>
<% } else { %>
<%- progress %>% <%- state %> <%- name %> <% if (service && service != "twitter_search") { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %>
<% } %>

View File

@@ -0,0 +1,47 @@
var CoreView = require('backbone/core-view');
var template = require('./background-import-limit.tpl');
var Notifier = require('builder/components/notifier/notifier');
/**
* Import limit message within background importer
*
*/
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._userModel = opts.userModel;
this._configModel = opts.configModel;
this._notification = Notifier.addNotification({
status: 'error',
closable: true,
button: false,
info: this._getInfo()
});
this._initBinds();
},
_initBinds: function () {
this._notification.on('notification:close', this._closeHandler, this);
this.add_related_model(this._notification);
},
_closeHandler: function () {
this.clean();
},
_getInfo: function () {
var importQuota = this._userModel.getMaxConcurrentImports();
var isUpgradeable = !this._configModel.get('cartodb_com_hosted') && importQuota === 1;
return template({
upgradeUrl: window.upgrade_url,
isUpgradeable: isUpgradeable,
importQuota: importQuota
});
}
});

View File

@@ -0,0 +1,5 @@
<% if (isUpgradeable) { %>
<%- _t('components.background-importer.background-import-limit.hurry', { upgradeUrl: upgradeUrl }) %>
<% } else { %>
<%- _t('components.background-importer.background-import-limit.one-file', { importQuota: importQuota }) %>
<% } %>

View File

@@ -0,0 +1,161 @@
var Backbone = require('backbone');
var _ = require('underscore');
var ImportItemView = require('./background-import-item-view');
var ImportLimitItemView = require('./background-import-limit-view');
var ImportsModel = require('builder/data/background-importer/imports-model');
/**
* Background polling manager
*
* It will pool all polling operations (imports) that happens
* in the Builder
*
*/
var BackgroundImporter = function (options) {
this.options = options || {};
this._importers = {};
this.initialize(this.options);
};
BackgroundImporter.prototype = {
initialize: function (opts) {
if (!opts.userModel) throw new Error('userModel is required');
if (!opts.configModel) throw new Error('configModel is required');
if (!opts.modals) throw new Error('modals is required');
if (!opts.pollingModel) throw new Error('pollingModel is required');
this._userModel = opts.userModel;
this._configModel = opts.configModel;
this._createVis = opts.createVis;
this._modals = opts.modals;
this._model = opts.pollingModel;
this._initBinds();
},
addView: function (view) {
this._importers[view.cid] = view;
},
removeView: function (view) {
delete this._importers[view.cid];
},
enable: function () {
this._model.startPollings();
},
disable: function () {
this._model.stopPollings();
},
removeImport: function (model) {
this._model.removeImportItem(model);
},
destroy: function () {
this._model.off(null, null, this);
},
_initBinds: function () {
this._model.on('importAdded', this._addImport, this);
this._model.on('importByUploadData', this._addDataset, this);
},
_addImport: function (m) {
var importItemView = new ImportItemView({
configModel: this._configModel,
showSuccessDetailsButton: this._model.get('showSuccessDetailsButton'),
modals: this._modals,
importModel: m,
userModel: this._userModel
});
importItemView.on('remove', this._removeImport, this);
this.addView(importItemView);
this.enable();
},
_addDataset: function (d) {
if (d) {
this._addImportsItem(d);
}
},
_onDroppedFile: function (files) {
if (files) {
this._addImportsItem({
type: 'file',
value: files,
create_vis: this._createVis
});
}
},
_addImportsItem: function (uploadData) {
if (this._model.canAddImport()) {
this._removeLimitItem();
} else {
this._addLimitItem();
return false;
}
var imp = new ImportsModel({}, {
upload: uploadData,
userModel: this._userModel,
configModel: this._configModel
});
this._model.addImportItem(imp);
},
_addLimitItem: function () {
if (!this._importLimit) {
var view = new ImportLimitItemView({
configModel: this._configModel,
userModel: this._userModel
});
this.addView(view);
this._importLimit = view;
}
},
_removeLimitItem: function () {
var view = this._importLimit;
if (view) {
view.clean();
this.removeView(view);
delete this._importLimit;
}
},
_removeImport: function (model, view) {
this._model.removeImportItem(model);
this.removeView(view);
}
};
// Supporting default Backbone events like on, off, trigger, listenTo etc
_.extend(BackgroundImporter.prototype, Backbone.Events, {
remove: function () {
this.stopListening();
}
});
var manager = (function () {
var initialized = false;
var importer;
return {
init: function (opts) {
if (!initialized) {
importer = new BackgroundImporter(opts);
}
return importer;
}
};
})();
module.exports = manager;

View File

@@ -0,0 +1,34 @@
var template = require('./failed.tpl');
function getAction () {
return 'show_errors';
}
function getButton () {
return _t('components.background-importer.background-importer-item.show');
}
function getClosable () {
return true;
}
function updateNotification (info) {
var data = {
info: info,
status: _status,
closable: getClosable(),
button: getButton(),
action: getAction()
};
_notification.update(data);
}
var _notification;
var _status;
module.exports = function (model, notification, status, info, showDetails) {
_notification = notification;
_status = status;
updateNotification(template(info));
};

View File

@@ -0,0 +1 @@
<%= _t('components.background-importer.background-importer-item.error-connecting', { name: name }) %> <% if (service) { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %>

View File

@@ -0,0 +1,38 @@
var template = require('./loading.tpl');
function getAction () {
return false;
}
function getButton () {
return false;
}
function getClosable () {
var state = _model.get('state');
var step = _model.get('step');
return state === 'uploading' && step === 'upload';
}
function updateNotification (info) {
var data = {
info: info,
status: _status,
closable: getClosable(),
button: getButton(),
action: getAction()
};
_notification.update(data);
}
var _model;
var _notification;
var _status;
module.exports = function (model, notification, status, info, showDetails) {
_model = model;
_notification = notification;
_status = status;
updateNotification(template(info));
};

View File

@@ -0,0 +1 @@
<span class='CDB-Text is-semibold'><%- progress %>% <%- state %></span> <%- name %> <% if (service && service != "twitter_search") { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %>

View File

@@ -0,0 +1,60 @@
var template = require('./success.tpl');
function getAction () {
var service = _model.get('upload').service_name;
var tables_created_count = _model.get('import').tables_created_count;
if (_showDetails) {
if (service && service === 'twitter_search') {
return 'show_stats';
} else if (tables_created_count === 1) {
return 'show_table';
}
}
return false;
}
function getButton () {
var service = _model.get('upload').service_name;
var tables_created_count = _model.get('import').tables_created_count;
if (_showDetails) {
if (service && service === 'twitter_search') {
return _t('components.background-importer.background-importer-item.show');
} else if (tables_created_count === 1) {
return _t('components.background-importer.background-importer-item.show');
}
}
return false;
}
function getClosable () {
return true;
}
function updateNotification (info) {
var data = {
info: info,
status: _status,
closable: getClosable(),
button: getButton(),
action: getAction()
};
_notification.update(data);
}
var _model;
var _notification;
var _status;
var _showDetails;
module.exports = function (model, notification, status, info, showDetails) {
_model = model;
_notification = notification;
_status = status;
_showDetails = showDetails;
updateNotification(template(info));
};

View File

@@ -0,0 +1 @@
<%- name %> <% if (service && service != "twitter_search") { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %> <span class='CDB-Text is-semibold'><%- _t('components.background-importer.background-importer-item.completed') %></span>

View File

@@ -0,0 +1,34 @@
var template = require('./warnings.tpl');
function getAction () {
return 'show_warnings';
}
function getButton () {
return _t('components.background-importer.background-importer-item.show');
}
function getClosable () {
return true;
}
function updateNotification (info) {
var data = {
info: info,
status: _status,
closable: getClosable(),
button: getButton(),
action: getAction()
};
_notification.update(data);
}
var _notification;
var _status;
module.exports = function (model, notification, status, info, showDetails) {
_notification = notification;
_status = status;
updateNotification(template(info));
};

View File

@@ -0,0 +1 @@
<span class='CDB-Text is-semibold'>Some warnings</span> were produced for <%- name %> <% if (service) { %> <%- _t('components.background-importer.background-importer-item.from') %> <%- service %> <% } %>

View File

@@ -0,0 +1,55 @@
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var defaultTemplate = require('./error-details.tpl');
var upgradeErrorTemplate = require('./upgrade-errors.tpl');
var UPGRADE_ERROR_CODES = [8001, 8002, 8005, 8007];
/**
* Error details view, to be used together with an error object from an import model.
*
*/
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');
if (!opts.error) throw new Error('error is required');
this._userModel = opts.userModel;
this._configModel = opts.configModel;
this._error = opts.error;
},
render: function () {
var isUpgradeError = _.contains(UPGRADE_ERROR_CODES, this._error.errorCode);
var upgradeUrl = this._configModel.get('upgrade_url');
var userCanUpgrade = upgradeUrl && !this._configModel.get('cartodb_com_hosted') && (!this._userModel.isInsideOrg() || this._userModel.isOrgOwner());
var template = this._getTemplate(isUpgradeError);
var html = template({
errorCode: this._error.errorCode,
title: this._error.title,
text: this._error.what_about,
itemQueueId: this._error.item_queue_id,
originalUrl: this._error.originalUrl,
httpResponseCode: this._error.httpResponseCode,
httpResponseCodeMessage: this._error.httpRresponseCodeMessage,
userCanUpgrade: userCanUpgrade,
showTrial: this._userModel.canStartTrial(),
upgradeUrl: upgradeUrl
});
this.$el.html(html);
return this;
},
_getTemplate: function (isUpgradeError) {
var template = defaultTemplate;
if (isUpgradeError) {
template = upgradeErrorTemplate;
}
return template;
}
});

View File

@@ -0,0 +1,72 @@
<div class="Dialog-header">
<div class="Dialog-headerIcon Dialog-headerIcon--negative u-flex u-alignCenter u-justifyCenter">
<i class="CDB-IconFont CDB-IconFont-cloud"></i>
</div>
<h2 class="CDB-Text CDB-Size-large u-bSpace u-errorTextColor">
<%- title %> <% if (errorCode) { %>(<%- errorCode %>)<% } %>
</h2>
<h3 class="CDB-Text CDB-Size-medium u-secondaryTextColor">
<% if (itemQueueId) { %>
<%- _t('components.background-importer.error-details.dont-panic') %>
<% } else { %>
<%- _t('components.background-importer.error-details.check-errors') %>
<% } %>
</h3>
</div>
<div class="Dialog-body ErrorDetails-body">
<ul class="Modal-containerList">
<% if (httpResponseCode) { %>
<li class="ErrorDetails-item">
<div class="ErrorDetails-itemStep CDB-Text CDB-Size-medium is-semibold u-flex u-alignCenter u-justifyCenter">1</div>
<div class="ErrorDetails-itemText">
<p class="CDB-Text CDB-Size-medium">
<%= _t('components.background-importer.error-details.remote-server-code', { httpResponseCode: httpResponseCode}) %> <%- httpResponseCodeMessage %>
</p>
</div>
</li>
<li class="ErrorDetails-item">
<div class="ErrorDetails-itemStep CDB-Text CDB-Size-medium is-semibold u-flex u-alignCenter u-justifyCenter">2</div>
<div class="ErrorDetails-itemText">
<p class="CDB-Text CDB-Size-medium">
<%- _t('components.background-importer.error-details.check-url') %>:<br/>
</p>
<span class='CDB-Text CDB-Size-medium ErrorDetails-itemTextStrong'><a href="<%- originalUrl %>"><%- originalUrl %></a></span>
</div>
</li>
<% } else { %>
<li class="ErrorDetails-item">
<div class="ErrorDetails-itemStep CDB-Text CDB-Size-medium is-semibold u-flex u-alignCenter u-justifyCenter">1</div>
<div class="ErrorDetails-itemText">
<p class="CDB-Text CDB-Size-medium">
<% if (text) { %>
<%= cdb.core.sanitize.html(text) %>
<% } else { %>
<%- _t('components.background-importer.error-details.unknown-error') %>
<% } %>
</p>
</div>
</li>
<% } %>
<% if (itemQueueId) { %>
<li class="ErrorDetails-item">
<div class="ErrorDetails-itemStep CDB-Text CDB-Size-medium is-semibold u-flex u-alignCenter u-justifyCenter">!</div>
<div class="ErrorDetails-itemText">
<p class="CDB-Text CDB-Size-medium">
<%= _t('components.background-importer.error-details.send-us-the-error-code') %>:<br/>
</p>
<span class="CDB-Text CDB-Size-medium ErrorDetails-itemTextStrong"><%- itemQueueId %></span>
</div>
</li>
<% } %>
</ul>
</div>
<div class="Dialog-footer--simple u-inner">
<button class="CDB-Button CDB-Button--error u-tSpace--m js-close">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
<%- _t('components.background-importer.error-details.close') %>
</span>
</button>
</div>

View File

@@ -0,0 +1,55 @@
var CoreView = require('backbone/core-view');
var Utils = require('builder/helpers/utils');
var template = require('./twitter-import-details.tpl');
const checkAndBuildOpts = require('builder/helpers/required-opts');
const REQUIRED_OPTS = [
'userModel',
'modalModel'
];
/**
* When a Twitter import finishes, this dialog displays
* all the info about the price/cost etc.
*
*/
module.exports = CoreView.extend({
className: 'TwitterImportDetails',
events: {
'click .js-close': '_onCloseClick'
},
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
},
render: function () {
var imp = this.model.get('import');
var userTwitterValues = this._userModel.get('twitter');
var availableTweets = userTwitterValues.quota - userTwitterValues.monthly_use;
this.$el.html(
template({
datasetTotalRows: imp.tweets_georeferenced,
datasetTotalRowsFormatted: Utils.formatNumber(imp.tweets_georeferenced),
tweetsCost: imp.tweets_cost,
tweetsCostFormatted: Utils.formatNumber(imp.tweets_cost),
availableTweets: availableTweets,
availableTweetsFormatted: Utils.formatNumber(availableTweets),
tweetsOverquota: imp.tweets_overquota,
tweetsOverquotaFormatted: Utils.formatNumber(imp.tweets_overquota),
blockSizeFormatted: Utils.formatNumber(userTwitterValues.block_size),
blockPriceFormatted: Utils.formatNumber(userTwitterValues.block_price)
})
);
return this;
},
_onCloseClick: function () {
this._modalModel.destroy();
}
});

View File

@@ -0,0 +1,53 @@
<div class="Dialog-header">
<div class="Dialog-headerIcon Dialog-headerIcon--twitter u-flex u-alignCenter u-justifyCenter">
<i class="CDB-IconFont CDB-IconFont-twitter"></i>
</div>
<h2 class="CDB-Text CDB-Size-large u-bSpace Dialog-headerIcon--twitter">
<%- _t('components.background-importer.twitter-import-details.twitter-import-title') %>
</h2>
<h3 class="CDB-Text CDB-Size-medium u-altTextColor">
<% if (datasetTotalRows === 0) { %>
<%- _t('components.background-importer.twitter-import-details.errors.no-results') %>
<% } else { %>
<%= _t('components.background-importer.twitter-import-details.new-type-created', { datasetTotalRowsFormatted: datasetTotalRowsFormatted, tweetPlural: datasetTotalRows != 1 ? 's' : '' }) %>
<% } %>
</h3>
</div>
<div class="Dialog-body ErrorDetails-body">
<ul class="Modal-containerList">
<li class="ErrorDetails-item">
<div class="ErrorDetails-itemIcon ErrorDetails-itemIcon--success CDB-Size-big u-flex u-alignCenter u-justifyCenter u-rSpace--xl">
<i class="CDB-IconFont CDB-IconFont-dollar"></i>
</div>
<div class="ErrorDetails-itemText">
<p class="CDB-Text CDB-Size-large u-secondaryTextColor">
<% if (tweetsCost > 0) { %>
<%- _t('components.background-importer.twitter-import-details.tweet-cost.paid', { tweetsCostFormatted: tweetsCostFormatted }) %>
<% } else { %>
<%- _t('components.background-importer.twitter-import-details.tweet-cost.free', { tweetsCostFormatted: tweetsCostFormatted }) %>
<% } %>
</p>
<p class="CDB-Text CDB-Size-medium u-altTextColor">
<% if (tweetsCost > 0 || availableTweets <= 0) { %>
<%- _t('components.background-importer.twitter-import-details.no-more-credits', { blockPriceFormatted: blockPriceFormatted, blockSizeFormatted: blockSizeFormatted }) %>
<% } else { %>
<% if (availableTweets != 1) { %>
<%- _t('components.background-importer.twitter-import-details.credits-left', { availableTweetsFormatted: availableTweetsFormatted }) %>
<% } else { %>
<%- _t('components.background-importer.twitter-import-details.credit-left', { availableTweetsFormatted: availableTweetsFormatted }) %>
<% } %>
<% } %>
</p>
</div>
</li>
</ul>
</div>
<div class="Dialog-footer--simple u-inner">
<button class="CDB-Button CDB-Button--primary u-tSpace--m">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase js-close">
<%- _t('components.background-importer.error-details.close') %>
</span>
</button>
</div>

View File

@@ -0,0 +1,38 @@
<div class="Dialog-header">
<div class="Dialog-headerIcon Dialog-headerIcon--negative u-flex u-alignCenter u-justifyCenter">
<i class="CDB-IconFont CDB-IconFont-barometer"></i>
</div>
<h2 class="CDB-Text CDB-Size-large u-bSpace u-errorTextColor">
<%- _t('components.background-importer.upgrade-errors.' + errorCode + '.title') %>
</h2>
<h3 class="CDB-Text CDB-Size-medium u-secondaryTextColor">
<%- _t('components.background-importer.upgrade-errors.' + errorCode + '.description') %>
</h3>
</div>
<div class="Dialog-body ErrorDetails-body">
<ul class="Modal-containerList">
<li class="ErrorDetails-item">
<div class="ErrorDetails-itemIcon ErrorDetails-itemIcon--success CDB-Size-big u-flex u-alignCenter u-justifyCenter u-rSpace--xl">
<i class="CDB-IconFont CDB-IconFont-rocket"></i>
</div>
<div class="ErrorDetails-itemText">
<p class="CDB-Text CDB-Size-medium">
<%- _t('components.background-importer.upgrade-errors.' + errorCode + '.info') %>
<% if (showTrial) { %>
<br/>
<a href="<%= upgradeUrl %>"><%- _t('components.background-importer.free-trial', { days: 14 }) %></a>
<% } %>
</p>
</div>
</li>
</ul>
</div>
<div class="Dialog-footer--simple u-inner">
<a href="<%- upgradeUrl %>" class="CDB-Button CDB-Button--primary u-tSpace--m">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
<%- _t('components.background-importer.upgrade-errors.upgrade') %>
</span>
</a>
</div>

View File

@@ -0,0 +1,29 @@
<div class="Dialog-header ErrorDetails-header">
<div class="Dialog-headerIcon Dialog-headerIcon--alert">
<i class="CDB-IconFont CDB-IconFont-cloud"></i>
</div>
<p class="Dialog-headerTitle--warning">
<%- _t('components.background-importer.partial-import-details.unable-to-import-as-layers') %>
</p>
<p class="Dialog-headerText">
<%- _t('components.background-importer.partial-import-details.find-connected-datasets') %></br>
<%- _t('components.background-importer.partial-import-details.upgrade-your-account', { userMaxLayers: userMaxLayers }) %></br>
</p>
</div>
<% if (maxTablesPerImport) { %>
<div class="Dialog-body ErrorDetails-body">
<ul class="ErrorDetails-list">
<li class="ErrorDetails-item">
<div class="ErrorDetails-itemStep">!</div>
<div class="ErrorDetails-itemText">
<%- _t('components.background-importer.partial-import-details.too-many-datasets', { maxTablesPerImport: maxTablesPerImport }) %></br>
</div>
</li>
</ul>
</div>
<% } %>
<div class="Dialog-footer ErrorDetails-footer">
<button class="Button Button--secondary ErrorDetails-footerButton js-close">
<span><%- _t('components.background-importer.partial-import-details.continue-btn') %></span>
</button>
</div>

View File

@@ -0,0 +1,17 @@
<div class="Dialog-header ErrorDetails-header">
<div class="Dialog-headerIcon Dialog-headerIcon--alert">
<i class="CDB-IconFont CDB-IconFont-cloud"></i>
</div>
<p class="Dialog-headerTitle--warning">
<%- _t('components.background-importer.warnings-details.unable-to-import-datasets') %>
</p>
<p class="Dialog-headerText">
<%- _t('components.background-importer.warnings-details.no-more-datasets', { maxTablesPerImport: maxTablesPerImport}) %><br />
<%- _t('components.background-importer.warnings-details.find-connected-datasets') %>
</p>
</div>
<div class="Dialog-footer ErrorDetails-footer">
<button class="Button Button--secondary ErrorDetails-footerButton u-upperCase js-close">
<span><%- _t('components.background-importer.warnings-details.continue-btn') %></span>
</button>
</div>

View File

@@ -0,0 +1,16 @@
<div class="Dialog-header ErrorDetails-header">
<div class="Dialog-headerIcon Dialog-headerIcon--alert">
<i class="CDB-IconFont CDB-IconFont-cloud"></i>
</div>
<p class="Dialog-headerTitle--warning">
<%- _t('components.background-importer.connector-warning-details.too-many-rows') %>
</p>
<p class="Dialog-headerText">
<%- _t('components.background-importer.connector-warning-details.unable-to-import-all-rows', { maxRowsPerConnectorImport: maxRowsPerConnectorImport}) %><br />
</p>
</div>
<div class="Dialog-footer ErrorDetails-footer">
<button class="Button Button--secondary ErrorDetails-footerButton u-upperCase js-close">
<span><%- _t('components.background-importer.connector-warning-details.continue-btn') %></span>
</button>
</div>

View File

@@ -0,0 +1,47 @@
var CoreView = require('backbone/core-view');
var partialImportDetailsTemplate = require('./warning-partial-import-details.tpl');
var tooManyFilesDetailsTemplate = require('./warning-too-many-files-details.tpl');
var tooManyRowsConnectorTemplate = require('./warning-too-many-rows-connector-details.tpl');
/**
* Error details view, to be used together with an error object from an import model.
*
*/
module.exports = CoreView.extend({
initialize: function (opts) {
if (!opts.userModel) throw new Error('userModel is required');
if (!opts.warnings) throw new Error('warnings is required');
this._userModel = this.options.userModel;
this._warnings = this.options.warnings;
},
render: function () {
var warnings = this._warnings;
var template = this._getTemplate(warnings);
this.$el.html(
template({
userMaxLayers: warnings.user_max_layers,
maxTablesPerImport: warnings.max_tables_per_import,
maxRowsPerConnectorImport: warnings.max_rows_per_connection
})
);
return this;
},
_getTemplate: function (warnings) {
if (warnings.user_max_layers && warnings.max_tables_per_import) {
return (warnings.user_max_layers < warnings.max_tables_per_import) ? partialImportDetailsTemplate : tooManyFilesDetailsTemplate;
} else if (warnings.user_max_layers) {
return partialImportDetailsTemplate;
} else if (warnings.max_tables_per_import) {
return tooManyFilesDetailsTemplate;
} else if (warnings.max_rows_per_connection) {
return tooManyRowsConnectorTemplate;
}
}
});

View File

@@ -0,0 +1,59 @@
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var CarouselView = require('./custom-carousel/custom-carousel-view');
/**
* Carousel form view
*/
module.exports = CoreView.extend({
module: 'components:carousel-form-view',
className: 'js-aggregationTypes',
initialize: function (opts) {
if (!opts.collection) throw new Error('Carousel collection is required');
if (!opts.template) throw new Error('template is required');
this.template = opts.template;
this._initBinds();
},
render: function () {
var selectedItem = this.collection.getSelected();
var selectedName = selectedItem && selectedItem.getName();
this.$el.html(
this.template({
name: selectedName
})
);
this._initViews();
return this;
},
_initBinds: function () {
this.collection.bind('change:highlighted', this._onChangeHighlighted, this);
},
_initViews: function () {
var carousel = new CarouselView(_.extend(this.options, {
collection: this.collection
}));
if (!this.$('.js-selector').length) throw new Error('HTML element with js-selector class is required');
this.$('.js-selector').append(carousel.render().el);
carousel.initScroll();
this.addView(carousel);
},
_onChangeHighlighted: function () {
var item = this.collection.getHighlighted() || this.collection.getSelected();
if (item) {
var $el = this.$('.js-highlight');
if ($el) {
$el.text(item.getName());
}
}
}
});

View File

@@ -0,0 +1,315 @@
var COLOR_KEYWORDS = require('builder/helpers/color-keywords');
/*
LESS mode - http://www.lesscss.org/
Ported to CodeMirror by Peter Kroon <plakroon@gmail.com>
Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues GitHub: @peterkroon
*/
module.exports = function (CodeMirror) {
CodeMirror.defineMode('cartocss', function (config) {
var indentUnit = config.indentUnit;
var type;
function ret (style, tp) {
type = tp;
return style;
}
// html tags
var tags = 'a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br button canvas caption cite code col colgroup command datalist dd del details dfn dir div dl dt em embed fieldset figcaption figure font footer form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins keygen kbd label legend li link map mark menu meta meter nav noframes noscript object ol optgroup option output p param pre progress q rp rt ruby s samp script section select small source span strike strong style sub summary sup table tbody td textarea tfoot th thead time title tr track tt u ul var video wbr'.split(' ');
var colorKeywords = keySet(COLOR_KEYWORDS);
function inTagsArray (val) {
for (var i = 0; i < tags.length; i++) {
if (val === tags[i]) return true;
}
}
var selectors = /(^\:root$|^\:nth\-child$|^\:nth\-last\-child$|^\:nth\-of\-type$|^\:nth\-last\-of\-type$|^\:first\-child$|^\:last\-child$|^\:first\-of\-type$|^\:last\-of\-type$|^\:only\-child$|^\:only\-of\-type$|^\:empty$|^\:link|^\:visited$|^\:active$|^\:hover$|^\:focus$|^\:target$|^\:lang$|^\:enabled^\:disabled$|^\:checked$|^\:first\-line$|^\:first\-letter$|^\:before$|^\:after$|^\:not$|^\:required$|^\:invalid$)/;
function tokenBase (stream, state) {
var ch = stream.next();
if (ch === '@') {
stream.eatWhile(/[\w\-]/);
return ret('meta', stream.current());
} else if (ch === '/' && stream.eat('*')) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else if (ch === '<' && stream.eat('!')) {
state.tokenize = tokenSGMLComment;
return tokenSGMLComment(stream, state);
} else if (ch === '=') ret(null, 'compare');
else if (ch === '|' && stream.eat('=')) return ret(null, 'compare');
else if (ch === '\'' || ch === '\'') {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch === '/') { // e.g.: .png will not be parsed as a class
if (stream.eat('/')) {
state.tokenize = tokenSComment;
return tokenSComment(stream, state);
} else {
if (type === 'string' || type === '(') {
return ret('string', 'string');
}
if (state.stack[state.stack.length - 1] !== undefined) {
return ret(null, ch);
}
stream.eatWhile(/[\a-zA-Z0-9\-_.\s]/);
if (/\/|\)|#/.test(stream.peek() || (stream.eatSpace() && stream.peek() === ')')) || stream.eol()) {
return ret('string', 'string'); // let url(/images/logo.png) without quotes return as string
}
}
} else if (ch === '!') {
stream.match(/^\s*\w*/);
return ret('keyword', 'important');
} else if (/\d/.test(ch)) {
stream.eatWhile(/[\w.%]/);
return ret('number', 'unit');
} else if (/[,+<>*\/]/.test(ch)) {
if (stream.peek() === '=' || type === 'a') {
return ret('string', 'string');
}
return ret(null, 'select-op');
} else if (/[;{}:\[\]()~\|]/.test(ch)) {
if (ch === ':') {
stream.eatWhile(/[a-z\\\-]/);
if (selectors.test(stream.current())) {
return ret('tag', 'tag');
} else if (stream.peek() === ':') { // ::-webkit-search-decoration
stream.next();
stream.eatWhile(/[a-z\\\-]/);
if (stream.current().match(/\:\:\-(o|ms|moz|webkit)\-/)) {
return ret('string', 'string');
}
if (selectors.test(stream.current().substring(1))) {
return ret('tag', 'tag');
}
return ret(null, ch);
} else {
return ret(null, ch);
}
} else if (ch === '~') {
if (type === 'r') {
return ret('string', 'string');
}
} else {
return ret(null, ch);
}
} else if (ch === '.') {
if (type === '(' || type === 'string') {
return ret('string', 'string'); // allow url(../image.png)
}
stream.eatWhile(/[\a-zA-Z0-9\-_]/);
if (stream.peek() === ' ') {
stream.eatSpace();
}
if (stream.peek() === ')') {
return ret('number', 'unit'); // rgba(0,0,0,.25);
}
return ret('tag', 'tag');
} else if (ch === '#') {
// we don't eat white-space, we want the hex color and or id only
stream.eatWhile(/[A-Za-z0-9]/);
// check if there is a proper hex color length e.g. #eee || #eeeEEE
if (stream.current().length === 4 || stream.current().length === 7) {
if (stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/, false) != null) { // is there a valid hex color value present in the current stream
// when not a valid hex value, parse as id
if (stream.current().substring(1) !== stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/, false)[0]) {
return ret('atom', 'tag');
}
// eat white-space
stream.eatSpace();
// when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,]
if (/[\/<>.({!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek())) {
return ret('atom', 'tag');
} else if (stream.peek() === '}') {
// #time { color: #aaa }
return ret('color', 'unit');
} else if (/[a-zA-Z\\]/.test(stream.peek())) {
// we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa
return ret('color', 'unit');
} else if (stream.eol()) {
// when a hex value is on the end of a line, parse as id
return ret('color', 'unit');
} else {
// default
return ret('color', 'unit');
}
} else { // when not a valid hexvalue in the current stream e.g. #footer
stream.eatWhile(/[\w\\\-]/);
return ret('atom', 'tag');
}
} else { // when not a valid hexvalue length
stream.eatWhile(/[\w\\\-]/);
return ret('atom', 'tag');
}
} else if (ch === '&') {
stream.eatWhile(/[\w\-]/);
return ret(null, ch);
} else {
stream.eatWhile(/[\w\\\-_%.{]/);
if (type === 'string') {
return ret('string', 'string');
} else if (stream.current().match(/(^http$|^https$)/) != null) {
stream.eatWhile(/[\w\\\-_%.{:\/]/);
return ret('string', 'string');
} else if (stream.peek() === '<' || stream.peek() === '>') {
return ret('tag', 'tag');
} else if (/\(/.test(stream.peek())) {
return ret(null, ch);
} else if (stream.peek() === '/' && state.stack[state.stack.length - 1] !== undefined) { // url(dir/center/image.png)
return ret('string', 'string');
} else if (stream.current().match(/\-\d|\-.\d/)) { // match e.g.: -5px -0.4 etc... only colorize the minus sign
// commment out these 2 comment if you want the minus sign to be parsed as null -500px
// stream.backUp(stream.current().length-1);
// return ret(null, ch); //console.log( stream.current() );
return ret('number', 'unit');
} else if (inTagsArray(stream.current().toLowerCase())) { // match html tags
return ret('tag', 'tag');
} else if (/\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() === '/')) && stream.current().indexOf('.') !== -1) {
if (stream.current().substring(stream.current().length - 1, stream.current().length) === '{') {
stream.backUp(1);
return ret('tag', 'tag');
} // end if
stream.eatSpace();
if (/[{<>.a-zA-Z\/]/.test(stream.peek()) || stream.eol()) return ret('tag', 'tag'); // e.g. button.icon-plus
return ret('string', 'string'); // let url(/images/logo.png) without quotes return as string
} else if (stream.eol() || stream.peek() === '[' || stream.peek() === '#' || type === 'tag') {
if (stream.current().substring(stream.current().length - 1, stream.current().length) === '{') stream.backUp(1);
return ret('tag', 'tag');
} else if (type === 'compare' || type === 'a' || type === '(') {
return ret('string', 'string');
} else if (type === '|' || stream.current() === '-' || type === '[') {
return ret(null, ch);
} else if (stream.peek() === ':') {
stream.next();
var t_v = stream.peek() === ':';
if (!t_v) {
var old_pos = stream.pos;
var sc = stream.current().length;
stream.eatWhile(/[a-z\\\-]/);
var new_pos = stream.pos;
if (stream.current().substring(sc - 1).match(selectors) != null) {
stream.backUp(new_pos - (old_pos - 1));
return ret('tag', 'tag');
} else stream.backUp(new_pos - (old_pos - 1));
} else {
stream.backUp(1);
}
if (t_v) return ret('tag', 'tag');
else return ret('variable', 'variable');
// It is a color variable?
} else if (colorKeywords.hasOwnProperty(stream.current())) {
return ret('color', 'unit');
} else {
return ret('variable', 'variable');
}
}
}
function keySet (array) {
var keys = {};
for (var i = 0; i < array.length; ++i) {
keys[array[i]] = true;
}
return keys;
}
function tokenSComment (stream, state) { // SComment = Slash comment
stream.skipToEnd();
state.tokenize = tokenBase;
return ret('comment', 'comment');
}
function tokenCComment (stream, state) {
var maybeEnd = false;
var ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch === '/') {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch === '*');
}
return ret('comment', 'comment');
}
function tokenSGMLComment (stream, state) {
var dashes = 0;
var ch;
while ((ch = stream.next()) != null) {
if (dashes >= 2 && ch === '>') {
state.tokenize = tokenBase;
break;
}
dashes = (ch === '-') ? dashes + 1 : 0;
}
return ret('comment', 'comment');
}
function tokenString (quote) {
return function (stream, state) {
var escaped = false;
var ch;
while ((ch = stream.next()) != null) {
if (ch === quote && !escaped) {
break;
}
escaped = !escaped && ch === '\\';
}
if (!escaped) state.tokenize = tokenBase;
return ret('string', 'string');
};
}
return {
startState: function (base) {
return {
tokenize: tokenBase,
baseIndent: base || 0,
stack: []
};
},
token: function (stream, state) {
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
var context = state.stack[state.stack.length - 1];
if (type === 'hash' && context === 'rule') style = 'atom';
else if (style === 'variable') {
if (context === 'rule') style = null; // 'tag'
else if (!context || context === '@media{') {
style = stream.current() === 'when' ? 'variable' : /[\s,|\s\)|\s]/.test(stream.peek()) ? 'tag' : type;
}
}
if (context === 'rule' && /^[\{\};]$/.test(type)) {
state.stack.pop();
}
if (type === '{') {
if (context === '@media') state.stack[state.stack.length - 1] = '@media{';
else state.stack.push('{');
} else if (type === '}') state.stack.pop();
else if (type === '@media') state.stack.push('@media');
else if (context === '{' && type !== 'comment') state.stack.push('rule');
return style;
},
indent: function (state, textAfter) {
var n = state.stack.length;
if (/^\}/.test(textAfter)) {
n -= state.stack[state.stack.length - 1] === 'rule' ? 2 : 1;
}
return state.baseIndent + n * indentUnit;
},
electricChars: '}'
};
});
CodeMirror.defineMIME('text/x-carto', 'cartocss');
};

View File

@@ -0,0 +1 @@
<i class="CodeMirror-bullet"></i>

View File

@@ -0,0 +1,5 @@
<ul class="CodeMirror-error">
<li class="CodeMirror-errorMessage u-lSpace--xl u-rSpace--xl">
<%- _t('components.codemirror.syntax-error') %>. <%- _t('components.codemirror.line') %> <%- line %>: <span><%- message %></span>
</li>
</ul>

View File

@@ -0,0 +1,365 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var CodeMirror = require('codemirror');
var ColorPicker = require('./colorpicker.code-mirror');
var template = require('./code-mirror.tpl');
var bulletTemplate = require('./code-mirror-bullet.tpl');
var errorTemplate = require('./code-mirror-error.tpl');
var warningTemplate = require('./code-mirror-warning.tpl');
var DATA_SERVICES = require('./data-services');
require('./mode/sql')(CodeMirror);
require('./mode/mustache')(CodeMirror);
require('./cartocss.code-mirror')(CodeMirror);
require('./scroll.code-mirror')(CodeMirror);
require('./show-hint.code-mirror')(CodeMirror);
require('./hint/custom-list-hint')(CodeMirror);
require('./searchcursor.code-mirror')(CodeMirror);
require('./placeholder.code-mirror')(CodeMirror);
var ESCAPE_KEY_CODE = 27;
var RETURN_KEY_CODE = 13;
var NOHINT = [ESCAPE_KEY_CODE, RETURN_KEY_CODE];
var ADDONS = {
'color-picker': ColorPicker
};
module.exports = CoreView.extend({
module: 'components:code-mirror:code-mirror-view',
className: 'Editor-content',
options: {
readonly: false,
lineNumbers: true,
autocompleteChars: 3
},
initialize: function (opts) {
if (!opts) throw new Error('options for codemirror are required.');
if (!opts.model) throw new Error('Model for codemirror is required.');
if (opts.model.get('content') === void 0 &&
opts.placeholder === void 0) throw new Error('Content property or placeholder for codemirror is required.');
if (!opts.tips) throw new Error('tip messages are required');
this._autocompleteChars = opts.autocompleteChars || this.options.autocompleteChars;
this._mode = opts.mode || 'cartocss';
this._addons = opts.addons;
this._hints = opts.hints;
this._autocompletePrefix = opts.autocompletePrefix;
this._autocompleteTriggers = opts.autocompleteTriggers;
this._autocompleteSuffix = opts.autocompleteSuffix;
this._errorTemplate = opts.errorTemplate || errorTemplate;
this._warningTemplate = opts.warningTemplate || warningTemplate;
this._warnings = null;
this._tips = opts.tips;
this._lineWithErrors = [];
this._onInputRead = _.bind(this._onKeyUpEditor, this);
this._placeholder = opts.placeholder;
},
render: function () {
this.$el.html(
template({
content: this.model.get('content'),
tips: this._tips.join(' '),
warnings: this._warnings
})
);
this._initViews();
this._bindEvents();
this._showErrors();
return this;
},
_initViews: function () {
var options = _.defaults(_.extend({}, this.model.toJSON()), this.options);
var isReadOnly = options.readonly;
var hasLineNumbers = options.lineNumbers;
var extraKeys = {
'Ctrl-S': this.triggerApplyEvent.bind(this),
'Cmd-S': this.triggerApplyEvent.bind(this),
'Ctrl-Space': this._completeIfAfterCtrlSpace.bind(this)
};
this.editor = CodeMirror.fromTextArea(this.$('.js-editor').get(0), {
lineNumbers: hasLineNumbers,
theme: 'material',
mode: this._mode,
scrollbarStyle: 'simple',
lineWrapping: true,
readOnly: isReadOnly,
extraKeys: extraKeys,
placeholder: this._placeholder
});
this.editor.on('change', _.debounce(this._onCodeMirrorChange.bind(this), 150), this);
if (!_.isEmpty(this._addons)) {
_.each(this._addons, function (addon) {
var Class = ADDONS[addon];
var addonView = new Class({
editor: this.editor
});
addonView.bind('codeSaved', this.triggerApplyEvent, this);
this.$el.append(addonView.el);
this.addView(addonView);
}, this);
}
if (this._hints) {
this.editor.on('keyup', this._onInputRead);
}
this._toggleReadOnly();
setTimeout(function () {
this.editor && this.editor.refresh();
}.bind(this), 0);
},
_completeIfAfterCtrlSpace: function (cm) {
var autocompletePrefix = this._autocompletePrefix;
var opts = {};
var cur = cm.getCursor();
if (autocompletePrefix &&
cm.getRange(CodeMirror.Pos(cur.line, cur.ch - autocompletePrefix.length), cur) !== autocompletePrefix) {
opts = { autocompletePrefix: autocompletePrefix };
}
return this._completeAfter(cm, opts);
},
updateHints: function (hints) {
this._hints = hints;
},
_onKeyUpEditor: function (cm, event) {
var code = event.keyCode;
var hints = this._hints;
var autocompleteChars = this._autocompleteChars - 1;
var autocompletePrefix = this._autocompletePrefix;
if (NOHINT.indexOf(code) === -1) {
var self = this;
if (this._autocompleteTimeout) clearTimeout(this._autocompleteTimeout);
this._autocompleteTimeout = setTimeout(function () {
var opts = {};
var cur = cm.getCursor();
var str = cm.getTokenAt(cur).string;
str = str.toLowerCase();
if (autocompletePrefix &&
cm.getRange(CodeMirror.Pos(cur.line, cur.ch - autocompletePrefix.length), cur) !== autocompletePrefix) {
opts = { autocompletePrefix: autocompletePrefix };
}
return self._completeAfter(cm, opts, function () {
var autocompleteHandler = function (listItem) {
// every list can be an array of strings or an array of objects {text, type}
var hit = _.isObject(listItem) ? listItem.text : listItem;
hit = hit.toLowerCase();
return hit.indexOf(str) !== -1;
};
if (str.length > autocompleteChars) {
var listHints = _.filter(hints, autocompleteHandler);
return listHints.length > 0 || autocompletePrefix && autocompletePrefix === str;
}
});
}, 150);
}
},
_onCodeMirrorChange: function () {
this.trigger('codeChanged');
},
_completeAfter: function (cm, opts, pred) {
if (!pred || pred()) {
if (!cm.state.completionActive) {
this._showAutocomplete(cm, _.extend({}, opts));
}
}
return CodeMirror.Pass;
},
_showAutocomplete: function (cm, opts) {
var autocompletePrefix = opts && opts.autocompletePrefix;
CodeMirror.showHint(cm, CodeMirror.hint['custom-list'], {
completeSingle: false,
list: this._hints,
autocompletePrefix: autocompletePrefix,
autocompleteSuffix: this._autocompleteSuffix
});
},
_showWarning: function (warnings) {
var $warning = this._getWarning();
var hasNodes = $warning.children().length;
if (warnings && !hasNodes) {
$warning.append(this._warningTemplate(warnings));
}
},
_hideWarning: function () {
var $warning = this._getWarning();
var hasNodes = $warning.children().length;
if (hasNodes) {
$warning.children()[0].remove();
}
},
_bindEvents: function () {
var self = this;
this.editor.on('change', function (editor, changed) {
var content = self.getContent();
var dataService = self._containsDataService(content);
if (dataService) {
self._showWarning('Quota error ' + dataService);
} else {
self._hideWarning();
}
self.model.set('content', content, { silent: true });
});
this.model.on('change:content', function () {
this.setContent(this.model.get('content'));
}, this);
this.model.on('change:readonly', this._toggleReadOnly, this);
this.model.on('change:errors', function () {
this._showErrors();
}, this);
this.model.on('undo redo', function () {
this.setContent(this.model.get('content'));
}, this);
},
_toggleReadOnly: function () {
var isReadOnly = !!this.model.get('readonly');
this.editor.setOption('readOnly', isReadOnly);
if (isReadOnly) {
this.editor.setOption('theme', '');
this._getInfo().hide();
} else {
this.editor.setOption('theme', 'material');
this._getInfo().show();
}
},
search: function (query, caseInsensitive) {
var cursor = this.editor.getSearchCursor(query, null, true);
cursor.find();
return cursor.pos;
},
markReadOnly: function (from, to) {
var options = {readOnly: true, inclusiveLeft: true};
this.editor.markText(from, to, options);
for (var i = from.line; i <= to.line; i++) {
this.editor.addLineClass(i, 'background', 'CodeMirror-readonlyLine');
}
},
setContent: function (value) {
this.editor.setValue(value);
},
getContent: function () {
return this.editor.getValue();
},
triggerApplyEvent: function () {
this.trigger('codeSaved', this.getContent(), this);
},
destroyEditor: function () {
this.editor.off('change');
var el = this.editor.getWrapperElement();
var parent = el.parentNode;
parent && parent.removeChild(el);
this.editor = null;
},
_getInfo: function () {
return this.$('.js-console');
},
_getConsole: function () {
return this.$('.js-console-error');
},
_getWarning: function () {
return this.$('.js-warning');
},
_getCode: function () {
return this.$('.CodeMirror-code');
},
_containsDataService: function (content) {
return _.find(DATA_SERVICES, function (dataService) {
return content.indexOf(dataService) !== -1;
});
},
_removeErrors: function () {
this._getConsole().empty();
_.each(this._lineWithErrors, function ($line) {
$line.find('.CodeMirror-bullet').remove();
$line.find('.CodeMirror-linenumber').removeClass('has-error');
});
this._lineWithErrors = [];
},
_showErrors: function () {
var errors = this.model.get('errors');
this._removeErrors();
if (errors && errors.length > 0) {
_.each(errors, function (err) {
this._renderError(err);
this._renderBullet(err);
}, this);
}
},
_renderBullet: function (error) {
var line = error.line;
var $line;
if (line) {
$line = this._getCode().children().eq(+line - 1);
$line.append(bulletTemplate);
$line.find('.CodeMirror-linenumber').addClass('has-error');
this._lineWithErrors.push($line);
}
},
_renderError: function (error) {
this._getConsole().append(this._errorTemplate(error));
},
clean: function () {
this.destroyEditor();
CoreView.prototype.clean.apply(this);
}
});

View File

@@ -0,0 +1,16 @@
<div class="CodeMirror-warning CDB-Text CDB-Size-medium u-flex u-lSpace--xl u-warningTextColor">
<div class="CodeMirror-warning-icon__wrapper">
<svg class="CodeMirror-warning-icon" width="100%" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>icon-font-114-Warning</title>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard-1" transform="translate(-9001.000000, -11000.000000)" fill-rule="nonzero" fill="#FEB100">
<g id="icon-font-114-Warning" transform="translate(9001.000000, 11000.000000)">
<path d="M47.3618699,443.171714 C40.4022689,456.721101 41.0006423,457.65748 56.75075,457.65748 L441.243856,457.65748 C456.985511,457.65748 457.59007,456.716689 450.632737,443.171714 L248.997303,50.6152213 L47.3618699,443.171714 Z M210.115762,31.2931921 C231.589444,-10.5131174 266.489496,-10.3489299 287.878844,31.2931921 L489.747111,424.302981 C511.220794,466.109288 489.302388,500 441.243856,500 L56.75075,500 C8.48945932,500 -13.1418536,465.945101 8.24749422,424.302981 L210.115762,31.2931921 Z M222.781864,372.97244 L222.781864,415.31496 L266.474263,415.31496 L266.474263,372.97244 L222.781864,372.97244 Z M222.781864,203.602361 L222.781864,330.62992 L266.474263,330.62992 L266.474263,203.602361 L222.781864,203.602361 Z" id="Combined-Shape"></path>
</g>
</g>
</g>
</svg>
</div>
<div><%= _t('editor.data.code-mirror.quota-data-services-warning') %> <%= _t('editor.data.code-mirror.quota-data-services-warning-link') %></div>
</div>

View File

@@ -0,0 +1,12 @@
<div class="CodeMirror-editor">
<textarea class="js-editor"><%- content %></textarea>
</div>
<div class="js-warning"></div>
<% if (tips) { %>
<div class="CodeMirror-console js-console">
<%- tips %>
<div class="js-console-error"></div>
</div>
<% } %>

View File

@@ -0,0 +1,225 @@
var CoreView = require('backbone/core-view');
var $ = require('jquery');
var _ = require('underscore');
var ColorPicker = require('builder/components/color-picker/color-picker.js');
var COLOR_KEYWORDS = require('builder/helpers/color-keywords');
/**
* Show color picker when user clicks over
* a color in the Codemirror editor.
*
* new CodemirrorColorPicker({
* editor: codemirror-editor...
* })
*/
var REQUIRED_OPTS = [
'editor'
];
var STYLE = _.template('1px solid <%- color %>');
var COLORPICKER_HEIGHT = 220;
var stopPropagation = function (e) {
e.stopPropagation();
};
module.exports = CoreView.extend({
initialize: function (opts) {
_.each(REQUIRED_OPTS, function (item) {
if (opts[item] === undefined) throw new Error(item + ' is required');
this['_' + item] = opts[item];
}, this);
this._updateColors = _.debounce(this._updateColors, 5).bind(this);
this._onDocumentClick = this._onDocumentClick.bind(this);
this._editor = opts.editor;
this._initBinds();
},
_initBinds: function () {
var self = this;
var destroyPicker = function () {
this._destroyPicker();
}.bind(this);
this._enableUpdateBind();
this._editor.on('mousedown', function (cm, ev) {
_.delay(self._onClick.bind(self, cm, ev), 50);
});
this._editor.on('keydown', destroyPicker);
this._editor.on('viewportChange', destroyPicker);
this._editor.on('scroll', destroyPicker);
var wrapper = this._editor.getWrapperElement();
wrapper.addEventListener('click', stopPropagation);
},
_disableBinds: function () {
var wrapper = this._editor.getWrapperElement();
wrapper.removeEventListener('click', stopPropagation);
this._editor.off(null, null, this);
},
_enableUpdateBind: function () {
this._editor.on('update', this._updateColors);
},
_disableUpdateBind: function () {
this._editor.off('update', this._updateColors);
},
_onClick: function (cm, ev) {
var cursor = this._editor.getCursor(true);
var token = this._editor.getTokenAt(cursor);
if (token.type === 'color') {
this._createPicker(ev, cursor, token);
} else {
this._destroyPicker();
}
},
_updateColors: function (cm) {
var wrapper = cm.getWrapperElement();
_.each(wrapper.querySelectorAll('.cm-color'), function (node) {
this._paintColor(node.textContent, node);
}, this);
},
_replaceColor: function (color, target) {
var cursor = this._editor.getCursor();
var nameMatch = this._getMatch(cursor, 'name');
var hexMatch = this._getMatch(cursor, 'hex');
var match = nameMatch || hexMatch;
var start;
var end;
if (match) {
start = {
line: cursor.line,
ch: match.start
};
end = {
line: cursor.line,
ch: match.end
};
this._editor.replaceRange(color, start, end, 'paste');
var wrapper = this._editor.getWrapperElement();
_.each(wrapper.querySelectorAll('.cm-color'), function (node) {
var nodeStyle = node.style;
var nodeColor = node.innerText;
if (!nodeStyle || !nodeStyle.borderBottom) {
this._paintColor(nodeColor, node);
}
}, this);
}
},
_paintColor: function (color, target) {
target.style.borderBottom = STYLE({color: color});
},
_getMatch: function (cursor, type) {
if (!type) return;
var re;
switch (type.toLowerCase()) {
case 'name':
re = new RegExp(COLOR_KEYWORDS.join('|'), 'g');
break;
case 'hsl':
re = /hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)/g;
break;
case 'rgb':
re = /rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/;
break;
case 'hex':
re = /#[a-fA-F0-9]{3,6}/g;
break;
default:
console.log('Invalid color match selection');
return;
}
var line = this._editor.getLine(cursor.line);
var match = re.exec(line);
while (match) {
var val = match[0];
var len = val.length;
var start = match.index;
var end = match.index + len;
if (cursor.ch >= start && cursor.ch <= end) {
match = null;
return {
start: start,
end: end,
string: val
};
}
match = re.exec(line);
}
},
_createPicker: function (ev, cursor, token) {
var cursorCoords = this._editor.cursorCoords();
this._destroyPicker();
this._colorPicker = new ColorPicker({
className: 'Editor-boxModal ColorPicker--cm Editor-boxModal--darked Editor-FormDialog CDB-Text',
value: token.string,
disableOpacity: true
});
this._colorPicker.$el.attr('data-colorpicker-cid', this.cid);
this._colorPicker.bind('change', _.debounce(this._onColorPickerChange.bind(this, ev), 5), this);
var top = cursorCoords.top + 20;
var maxTop = $(window).outerHeight();
if (top + COLORPICKER_HEIGHT > maxTop) {
top = cursorCoords.top - COLORPICKER_HEIGHT - 20;
}
this._colorPicker.$el.css({
left: cursorCoords.left,
top: top
});
document.body.appendChild(this._colorPicker.render().el);
document.addEventListener('click', this._onDocumentClick);
},
_onDocumentClick: function (e) {
var $el = $(e.target);
if ($el.closest('[data-colorpicker-cid="' + this.cid + '"]').length === 0) {
this._destroyPicker();
}
},
_onColorPickerChange: function (ev, values) {
this._disableUpdateBind();
this._replaceColor(values.hex, ev.target);
this.trigger('codeSaved');
this._enableUpdateBind();
},
_destroyPicker: function () {
if (this._colorPicker) {
this.removeView(this._colorPicker);
this._colorPicker.clean();
delete this._colorPicker;
}
document.removeEventListener('click', this._onDocumentClick);
},
clean: function () {
this._disableBinds();
CoreView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,8 @@
module.exports = [
'cdb_geocode_street_point',
'cdb_isodistance',
'cdb_isochrone',
'OBS_GetDemographicSnapshot',
'cdb_route_point_to_point',
'cdb_route_with_waypoints'
];

View File

@@ -0,0 +1,95 @@
var _ = require('underscore');
module.exports = function (CodeMirror) {
var Pos = CodeMirror.Pos;
function arrayContains (arr, item) {
return arr.indexOf(item) !== -1;
}
function scriptHint (editor, keywords, getToken, options) {
// Find the token at the cursor
var cur = editor.getCursor();
var token = getToken(editor, cur);
var tprop = token;
var context = [];
token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_-]*$/.test(token.string)) {
token = tprop = {
start: cur.ch,
end: cur.ch,
string: '',
state: token.state,
type: token.string === '.' ? 'property' : null
};
}
// If it is a property, find out what it is a property of.
while (tprop.type === 'property') {
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (tprop.string !== '.') return;
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (tprop.string === ')') {
var level = 1;
do {
tprop = getToken(editor, Pos(cur.line, tprop.start));
switch (tprop.string) {
case ')':
level++;
break;
case '(':
level--;
break;
default:
break;
}
} while (level > 0);
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (tprop.type.indexOf('variable') === 0) tprop.type = 'function';
else return; // no clue
}
context.push(tprop);
}
return {
list: getCompletions(token, context, keywords, options),
from: Pos(cur.line, token.start),
to: Pos(cur.line, token.end)
};
}
function columnsHint (editor, options) {
return scriptHint(editor, [], /* javascriptKeywords */
function (e, cur) {
return e.getTokenAt(cur);
},
options);
}
CodeMirror.registerHelper('hint', 'custom-list', columnsHint);
function getCompletions (token, context, keywords, options) {
var found = [];
var start = token.string.toLowerCase();
function maybeAdd (str) {
var hit = _.isObject(str) ? str.text : str;
hit = hit.toLowerCase();
if (hit.indexOf(start) === 0 && start !== hit && !arrayContains(found, hit)) {
found.push(str);
}
}
function gatherCompletions (obj) {
for (var name in obj) {
maybeAdd(obj[name]);
}
}
gatherCompletions(options.list);
_.each(keywords, maybeAdd);
return found;
}
};

View File

@@ -0,0 +1,70 @@
module.exports = function (CodeMirror) {
CodeMirror.defineMode('mustache', function () {
return {
token: function (stream, state) {
var ch;
if (stream.match('{{')) {
ch = stream.peek();
if (ch == null || ch != null && ch.match(/[{]{1,}/)) {
stream.next();
return 'mustache-error';
} else if (ch != null && ch.match(/[a-zA-Z\u00C0-\u024F_]+/)) {
return null;
}
}
if (stream.match('}}')) {
ch = stream.peek();
if (ch != null && ch.match(/[}]{1,}/)) {
stream.next();
return 'mustache-error';
} else if (ch == null) {
return null;
}
}
if (stream.match('}')) {
ch = stream.peek();
if (ch == null || ch != null && ch.match(/[}]{1,}/)) {
stream.next();
return 'mustache-error';
}
}
if (stream.match('{')) {
ch = stream.peek();
if (ch == null || ch != null && ch.match(/[{]{1,}/)) {
stream.next();
return 'mustache-error';
}
}
// Delimiter character
if (stream.match(/[a-zA-Z\u00C0-\u024F_]+/, true)) {
return 'mustache-text';
}
// jump to the next item, needed OR CRASH
stream.next();
}
};
});
(function () {
var sqlKeywords = '{{ }}';
function set (str) {
var obj = {};
var words = str.split(' ');
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
CodeMirror.defineMIME('text/mustache', {
name: 'mustache',
client: set('source'),
keywords: set(sqlKeywords)
});
}());
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,57 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
/* eslint-disable */
module.exports = function (CodeMirror) {
CodeMirror.defineOption("placeholder", "", function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
if (val && !prev) {
cm.on("blur", onBlur);
cm.on("change", onChange);
cm.on("swapDoc", onChange);
onChange(cm);
} else if (!val && prev) {
cm.off("blur", onBlur);
cm.off("change", onChange);
cm.off("swapDoc", onChange);
clearPlaceholder(cm);
var wrapper = cm.getWrapperElement();
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
}
if (val && !cm.hasFocus()) onBlur(cm);
});
function clearPlaceholder(cm) {
if (cm.state.placeholder) {
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);
cm.state.placeholder = null;
}
}
function setPlaceholder(cm) {
clearPlaceholder(cm);
var elt = cm.state.placeholder = document.createElement("pre");
elt.style.cssText = "height: 0; overflow: visible";
elt.className = "CodeMirror-placeholder";
var placeHolder = cm.getOption("placeholder")
if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
elt.appendChild(placeHolder)
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
}
function onBlur(cm) {
if (isEmpty(cm)) setPlaceholder(cm);
}
function onChange(cm) {
var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : "");
if (empty) setPlaceholder(cm);
else clearPlaceholder(cm);
}
function isEmpty(cm) {
return (cm.lineCount() === 1) && (cm.getLine(0) === "");
}
};
/* eslint-enable */

View File

@@ -0,0 +1,151 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
module.exports = function (CodeMirror) {
function Bar (cls, orientation, scroll) {
this.orientation = orientation;
this.scroll = scroll;
this.screen = this.total = this.size = 1;
this.pos = 0;
this.node = document.createElement('div');
this.node.className = cls + '-' + orientation;
this.inner = this.node.appendChild(document.createElement('div'));
var self = this;
CodeMirror.on(this.inner, 'mousedown', function (e) {
if (e.which !== 1) return;
CodeMirror.e_preventDefault(e);
var axis = self.orientation === 'horizontal' ? 'pageX' : 'pageY';
var start = e[axis];
var startpos = self.pos;
function done () {
CodeMirror.off(document, 'mousemove', move);
CodeMirror.off(document, 'mouseup', done);
}
function move (e) {
if (e.which !== 1) return done();
self.moveTo(startpos + (e[axis] - start) * (self.total / self.size));
}
CodeMirror.on(document, 'mousemove', move);
CodeMirror.on(document, 'mouseup', done);
});
CodeMirror.on(this.node, 'click', function (e) {
CodeMirror.e_preventDefault(e);
var innerBox = self.inner.getBoundingClientRect();
var where;
if (self.orientation === 'horizontal') {
where = e.clientX < innerBox.left ? -1 : e.clientX > innerBox.right ? 1 : 0;
} else {
where = e.clientY < innerBox.top ? -1 : e.clientY > innerBox.bottom ? 1 : 0;
}
self.moveTo(self.pos + where * self.screen);
});
function onWheel (e) {
var moved = CodeMirror.wheelEventPixels(e)[self.orientation === 'horizontal' ? 'x' : 'y'];
var oldPos = self.pos;
self.moveTo(self.pos + moved);
if (self.pos !== oldPos) {
CodeMirror.e_preventDefault(e);
}
}
CodeMirror.on(this.node, 'mousewheel', onWheel);
CodeMirror.on(this.node, 'DOMMouseScroll', onWheel);
}
Bar.prototype.setPos = function (pos, force) {
if (pos < 0) pos = 0;
if (pos > this.total - this.screen) pos = this.total - this.screen;
if (!force && pos === this.pos) return false;
this.pos = pos;
this.inner.style[this.orientation === 'horizontal' ? 'left' : 'top'] =
(pos * (this.size / this.total)) + 'px';
return true;
};
Bar.prototype.moveTo = function (pos) {
if (this.setPos(pos)) this.scroll(pos, this.orientation);
};
var minButtonSize = 10;
Bar.prototype.update = function (scrollSize, clientSize, barSize) {
var sizeChanged = this.screen !== clientSize || this.total !== scrollSize || this.size !== barSize;
if (sizeChanged) {
this.screen = clientSize;
this.total = scrollSize;
this.size = barSize;
}
var buttonSize = this.screen * (this.size / this.total);
if (buttonSize < minButtonSize) {
this.size -= minButtonSize - buttonSize;
buttonSize = minButtonSize;
}
this.inner.style[this.orientation === 'horizontal' ? 'width' : 'height'] =
buttonSize + 'px';
this.setPos(this.pos, sizeChanged);
};
function SimpleScrollbars (cls, place, scroll) {
this.addClass = cls;
this.horiz = new Bar(cls, 'horizontal', scroll);
place(this.horiz.node);
this.vert = new Bar(cls, 'vertical', scroll);
place(this.vert.node);
this.width = null;
}
SimpleScrollbars.prototype.update = function (measure) {
if (this.width == null) {
var style = window.getComputedStyle ? window.getComputedStyle(this.horiz.node) : this.horiz.node.currentStyle;
if (style) {
this.width = parseInt(style.height, 10);
}
}
var width = this.width || 0;
var needsH = measure.scrollWidth > measure.clientWidth + 1;
var needsV = measure.scrollHeight > measure.clientHeight + 1;
this.vert.node.style.display = needsV ? 'block' : 'none';
this.horiz.node.style.display = needsH ? 'block' : 'none';
if (needsV) {
this.vert.update(measure.scrollHeight, measure.clientHeight,
measure.viewHeight - (needsH ? width : 0));
this.vert.node.style.bottom = needsH ? width + 'px' : '0';
}
if (needsH) {
this.horiz.update(measure.scrollWidth, measure.clientWidth,
measure.viewWidth - (needsV ? width : 0) - measure.barLeft);
this.horiz.node.style.right = needsV ? width + 'px' : '0';
this.horiz.node.style.left = measure.barLeft + 'px';
}
return {right: needsV ? width : 0, bottom: needsH ? width : 0};
};
SimpleScrollbars.prototype.setScrollTop = function (pos) {
this.vert.setPos(pos);
};
SimpleScrollbars.prototype.setScrollLeft = function (pos) {
this.horiz.setPos(pos);
};
SimpleScrollbars.prototype.clear = function () {
var parent = this.horiz.node.parentNode;
parent.removeChild(this.horiz.node);
parent.removeChild(this.vert.node);
};
CodeMirror.scrollbarModel.simple = function (place, scroll) {
return new SimpleScrollbars('CodeMirror-simplescroll', place, scroll);
};
CodeMirror.scrollbarModel.overlay = function (place, scroll) {
return new SimpleScrollbars('CodeMirror-overlayscroll', place, scroll);
};
};

View File

@@ -0,0 +1,183 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
module.exports = function (CodeMirror) {
/*eslint-disable */
var Pos = CodeMirror.Pos;
function SearchCursor(doc, query, pos, caseFold) {
this.atOccurrence = false; this.doc = doc;
if (caseFold == null && typeof query == "string") caseFold = false;
pos = pos ? doc.clipPos(pos) : Pos(0, 0);
this.pos = {from: pos, to: pos};
// The matches method is filled in based on the type of query.
// It takes a position and a direction, and returns an object
// describing the next occurrence of the query, or null if no
// more matches were found.
if (typeof query != "string") { // Regexp match
if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
this.matches = function(reverse, pos) {
if (reverse) {
query.lastIndex = 0;
var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
for (;;) {
query.lastIndex = cutOff;
var newMatch = query.exec(line);
if (!newMatch) break;
match = newMatch;
start = match.index;
cutOff = match.index + (match[0].length || 1);
if (cutOff == line.length) break;
}
var matchLen = (match && match[0].length) || 0;
if (!matchLen) {
if (start == 0 && line.length == 0) {match = undefined;}
else if (start != doc.getLine(pos.line).length) {
matchLen++;
}
}
} else {
query.lastIndex = pos.ch;
var line = doc.getLine(pos.line), match = query.exec(line);
var matchLen = (match && match[0].length) || 0;
var start = match && match.index;
if (start + matchLen != line.length && !matchLen) matchLen = 1;
}
if (match && matchLen)
return {from: Pos(pos.line, start),
to: Pos(pos.line, start + matchLen),
match: match};
};
} else { // String query
var origQuery = query;
if (caseFold) query = query.toLowerCase();
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
var target = query.split("\n");
// Different methods for single-line and multi-line queries
if (target.length == 1) {
if (!query.length) {
// Empty string would match anything and never progress, so
// we define it to match nothing instead.
this.matches = function() {};
} else {
this.matches = function(reverse, pos) {
if (reverse) {
var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
var match = line.lastIndexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match);
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
} else {
var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
var match = line.indexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match) + pos.ch;
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
}
};
}
} else {
var origTarget = origQuery.split("\n");
this.matches = function(reverse, pos) {
var last = target.length - 1;
if (reverse) {
if (pos.line - (target.length - 1) < doc.firstLine()) return;
if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
var to = Pos(pos.line, origTarget[last].length);
for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
if (target[i] != fold(doc.getLine(ln))) return;
var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
return {from: Pos(ln, cut), to: to};
} else {
if (pos.line + (target.length - 1) > doc.lastLine()) return;
var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
var from = Pos(pos.line, cut);
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
if (target[i] != fold(doc.getLine(ln))) return;
if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
return {from: from, to: Pos(ln, origTarget[last].length)};
}
};
}
}
}
SearchCursor.prototype = {
findNext: function() {return this.find(false);},
findPrevious: function() {return this.find(true);},
find: function(reverse) {
var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
function savePosAndFail(line) {
var pos = Pos(line, 0);
self.pos = {from: pos, to: pos};
self.atOccurrence = false;
return false;
}
for (;;) {
if (this.pos = this.matches(reverse, pos)) {
this.atOccurrence = true;
return this.pos.match || true;
}
if (reverse) {
if (!pos.line) return savePosAndFail(0);
pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
}
else {
var maxLine = this.doc.lineCount();
if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
pos = Pos(pos.line + 1, 0);
}
}
},
from: function() {if (this.atOccurrence) return this.pos.from;},
to: function() {if (this.atOccurrence) return this.pos.to;},
replace: function(newText, origin) {
if (!this.atOccurrence) return;
var lines = CodeMirror.splitLines(newText);
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
}
};
// Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos) {
if (orig.length == folded.length) return pos;
for (var pos1 = Math.min(pos, orig.length);;) {
var len1 = orig.slice(0, pos1).toLowerCase().length;
if (len1 < pos) ++pos1;
else if (len1 > pos) --pos1;
else return pos1;
}
}
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold);
});
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this, query, pos, caseFold);
});
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
var ranges = [];
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
while (cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
ranges.push({anchor: cur.from(), head: cur.to()});
}
if (ranges.length)
this.setSelections(ranges, 0);
});
/* eslint-enable */
};

View File

@@ -0,0 +1,460 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
/* eslint-disable */
module.exports = function (CodeMirror) {
"use strict";
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
// This is the old interface, kept around for now to stay
// backwards-compatible.
CodeMirror.showHint = function(cm, getHints, options) {
if (!getHints) return cm.showHint(options);
if (options && options.async) getHints.async = true;
var newOpts = {hint: getHints};
if (options) for (var prop in options) newOpts[prop] = options[prop];
return cm.showHint(newOpts);
};
CodeMirror.defineExtension("showHint", function(options) {
options = parseOptions(this, this.getCursor("start"), options);
var selections = this.listSelections()
if (selections.length > 1) return;
// By default, don't allow completion when something is selected.
// A hint function can have a `supportsSelection` property to
// indicate that it can handle selections.
if (this.somethingSelected()) {
if (!options.hint.supportsSelection) return;
// Don't try with cross-line selections
for (var i = 0; i < selections.length; i++)
if (selections[i].head.line != selections[i].anchor.line) return;
}
if (this.state.completionActive) this.state.completionActive.close();
var completion = this.state.completionActive = new Completion(this, options);
if (!completion.options.hint) return;
CodeMirror.signal(this, "startCompletion", this);
completion.update(true);
});
function Completion(cm, options) {
this.cm = cm;
this.options = options;
this.widget = null;
this.debounce = 0;
this.tick = 0;
this.startPos = this.cm.getCursor("start");
this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
var self = this;
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
}
var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
return setTimeout(fn, 1000/60);
};
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
Completion.prototype = {
close: function() {
if (!this.active()) return;
this.cm.state.completionActive = null;
this.tick = null;
this.cm.off("cursorActivity", this.activityFunc);
if (this.widget && this.data) CodeMirror.signal(this.data, "close");
if (this.widget) this.widget.close();
CodeMirror.signal(this.cm, "endCompletion", this.cm);
},
active: function() {
return this.cm.state.completionActive == this;
},
pick: function(data, i) {
var completion = data.list[i];
if (completion.hint) completion.hint(this.cm, data, completion);
else this.cm.replaceRange(getText(completion, this.options), completion.from || data.from,
completion.to || data.to, "complete");
CodeMirror.signal(data, "pick", completion);
this.close();
},
cursorActivity: function() {
if (this.debounce) {
cancelAnimationFrame(this.debounce);
this.debounce = 0;
}
var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
(pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
this.close();
} else {
var self = this;
this.debounce = requestAnimationFrame(function() {self.update();});
if (this.widget) this.widget.disable();
}
},
update: function(first) {
if (this.tick == null) return
var self = this, myTick = ++this.tick
fetchHints(this.options.hint, this.cm, this.options, function(data) {
if (self.tick == myTick) self.finishUpdate(data, first)
})
},
finishUpdate: function(data, first) {
if (this.data) CodeMirror.signal(this.data, "update");
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
if (this.widget) this.widget.close();
if (data && this.data && isNewCompletion(this.data, data)) return;
this.data = data;
if (data && data.list.length) {
if (picked && data.list.length == 1) {
this.pick(data, 0);
} else {
this.widget = new Widget(this, data);
CodeMirror.signal(data, "shown");
}
}
}
};
function isNewCompletion(old, nw) {
var moved = CodeMirror.cmpPos(nw.from, old.from)
return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch
}
function parseOptions(cm, pos, options) {
var editor = cm.options.hintOptions;
var out = {};
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
if (editor) for (var prop in editor)
if (editor[prop] !== undefined) out[prop] = editor[prop];
if (options) for (var prop in options)
if (options[prop] !== undefined) out[prop] = options[prop];
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
return out;
}
function getText(completion, options) {
var autocompleteSuffix = options && options.autocompleteSuffix ? options.autocompleteSuffix : '';
var autocompletePrefix = options && options.autocompletePrefix ? options.autocompletePrefix : '';
if (typeof completion !== "string") {
completion = completion.text
}
return autocompletePrefix + completion + autocompleteSuffix;
}
function getType(completion) {
if (typeof completion == "string") return '';
else return completion.type;
}
function buildKeyMap(completion, handle) {
var baseMap = {
Up: function() {handle.moveFocus(-1);},
Down: function() {handle.moveFocus(1);},
PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
Home: function() {handle.setFocus(0);},
End: function() {handle.setFocus(handle.length - 1);},
Enter: handle.pick,
Tab: handle.pick,
Esc: handle.close
};
var custom = completion.options.customKeys;
var ourMap = custom ? {} : baseMap;
function addBinding(key, val) {
var bound;
if (typeof val != "string")
bound = function(cm) { return val(cm, handle); };
// This mechanism is deprecated
else if (baseMap.hasOwnProperty(val))
bound = baseMap[val];
else
bound = val;
ourMap[key] = bound;
}
if (custom)
for (var key in custom) if (custom.hasOwnProperty(key))
addBinding(key, custom[key]);
var extra = completion.options.extraKeys;
if (extra)
for (var key in extra) if (extra.hasOwnProperty(key))
addBinding(key, extra[key]);
return ourMap;
}
function getHintElement(hintsElement, el) {
while (el && el != hintsElement) {
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
el = el.parentNode;
}
}
function styleHint(hint) {
var element = document.createElement(hint.displayText || getText(hint));
element = document.createElement("span");
element.className = 'CDB-Size-small has-letter';
element.innerHTML = hint.displayText || getText(hint);
var type = getType(hint);
if (type) {
element.className = 'CDB-Size-small has-letter';
element.dataset.type = type;
}
return element;
}
function Widget(completion, data) {
this.completion = completion;
this.data = data;
this.picked = false;
var widget = this, cm = completion.cm;
var hints = this.hints = document.createElement("ul");
hints.className = "CodeMirror-hints";
this.selectedHint = data.selectedHint || 0;
var element;
var completions = data.list;
for (var i = 0; i < completions.length; ++i) {
var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
if (cur.className != null) className = cur.className + " " + className;
elt.className = className;
if (cur.render) cur.render(elt, data, cur);
else {
element = styleHint(cur);
elt.appendChild(element);
}
elt.hintId = i;
}
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
var left = pos.left, top = pos.bottom, below = true;
hints.style.left = left + "px";
hints.style.top = top + "px";
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
(completion.options.container || document.body).appendChild(hints);
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
if (overlapY > 0) {
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor
hints.style.top = (top = pos.top - height) + "px";
below = false;
} else if (height > winH) {
hints.style.height = (winH - 5) + "px";
hints.style.top = (top = pos.bottom - box.top) + "px";
var cursor = cm.getCursor();
if (data.from.ch != cursor.ch) {
pos = cm.cursorCoords(cursor);
hints.style.left = (left = pos.left) + "px";
box = hints.getBoundingClientRect();
}
}
}
var overlapX = box.right - winW;
if (overlapX > 0) {
if (box.right - box.left > winW) {
hints.style.width = (winW - 5) + "px";
overlapX -= (box.right - box.left) - winW;
}
hints.style.left = (left = pos.left - overlapX) + "px";
}
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
setFocus: function(n) { widget.changeActive(n); },
menuSize: function() { return widget.screenAmount(); },
length: completions.length,
close: function() { completion.close(); },
pick: function() { widget.pick(); },
data: data
}));
if (completion.options.closeOnUnfocus) {
var closingOnBlur;
cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
}
var startScroll = cm.getScrollInfo();
cm.on("scroll", this.onScroll = function() {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
var newTop = top + startScroll.top - curScroll.top;
var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
if (!below) point += hints.offsetHeight;
if (point <= editor.top || point >= editor.bottom) return completion.close();
hints.style.top = newTop + "px";
hints.style.left = (left + startScroll.left - curScroll.left) + "px";
});
CodeMirror.on(hints, "dblclick", function(e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
});
CodeMirror.on(hints, "click", function(e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
if (completion.options.completeOnSingleClick) widget.pick();
}
});
CodeMirror.on(hints, "mousedown", function() {
setTimeout(function(){cm.focus();}, 20);
});
CodeMirror.signal(data, "select", completions[0], hints.firstChild);
return true;
}
Widget.prototype = {
close: function() {
if (this.completion.widget != this) return;
this.completion.widget = null;
this.hints.parentNode.removeChild(this.hints);
this.completion.cm.removeKeyMap(this.keyMap);
var cm = this.completion.cm;
if (this.completion.options.closeOnUnfocus) {
cm.off("blur", this.onBlur);
cm.off("focus", this.onFocus);
}
cm.off("scroll", this.onScroll);
},
disable: function() {
this.completion.cm.removeKeyMap(this.keyMap);
var widget = this;
this.keyMap = {Enter: function() { widget.picked = true; }};
this.completion.cm.addKeyMap(this.keyMap);
},
pick: function() {
this.completion.pick(this.data, this.selectedHint);
},
changeActive: function(i, avoidWrap) {
if (i >= this.data.list.length)
i = avoidWrap ? this.data.list.length - 1 : 0;
else if (i < 0)
i = avoidWrap ? 0 : this.data.list.length - 1;
if (this.selectedHint == i) return;
var node = this.hints.childNodes[this.selectedHint];
node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
node = this.hints.childNodes[this.selectedHint = i];
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
if (node.offsetTop < this.hints.scrollTop)
this.hints.scrollTop = node.offsetTop - 3;
else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
},
screenAmount: function() {
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
}
};
function applicableHelpers(cm, helpers) {
if (!cm.somethingSelected()) return helpers
var result = []
for (var i = 0; i < helpers.length; i++)
if (helpers[i].supportsSelection) result.push(helpers[i])
return result
}
function fetchHints(hint, cm, options, callback) {
if (hint.async) {
hint(cm, callback, options)
} else {
var result = hint(cm, options)
if (result && result.then) result.then(callback)
else callback(result)
}
}
function resolveAutoHints(cm, pos) {
var helpers = cm.getHelpers(pos, "hint"), words
if (helpers.length) {
var resolved = function(cm, callback, options) {
var app = applicableHelpers(cm, helpers);
function run(i) {
if (i == app.length) return callback(null)
fetchHints(app[i], cm, options, function(result) {
if (result && result.list.length > 0) callback(result)
else run(i + 1)
})
}
run(0)
}
resolved.async = true
resolved.supportsSelection = true
return resolved
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
} else if (CodeMirror.hint.anyword) {
return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
} else {
return function() {}
}
}
CodeMirror.registerHelper("hint", "auto", {
resolve: resolveAutoHints
});
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
var to = CodeMirror.Pos(cur.line, token.end);
if (token.string && /\w/.test(token.string[token.string.length - 1])) {
var term = token.string, from = CodeMirror.Pos(cur.line, token.start);
} else {
var term = "", from = to;
}
var found = [];
for (var i = 0; i < options.words.length; i++) {
var word = options.words[i];
if (word.slice(0, term.length) == term)
found.push(word);
}
if (found.length) return {list: found, from: from, to: to};
});
CodeMirror.commands.autocomplete = CodeMirror.showHint;
var defaultOptions = {
hint: CodeMirror.hint.auto,
completeSingle: true,
alignWithWord: true,
closeCharacters: /[\s()\[\]{};:>,]/,
closeOnUnfocus: true,
completeOnSingleClick: true,
container: null,
customKeys: null,
extraKeys: null
};
CodeMirror.defineOption("hintOptions", null);
};
/* eslint-enable */

View File

@@ -0,0 +1,14 @@
<div class="colorpicker dropdown-menu">
<div class="colorpicker-saturation">
<i><b></b></i>
</div>
<div class="colorpicker-hue">
<i class="ColorPicker-ball"></i>
</div>
<div class="colorpicker-alpha js-alpha">
<i class="ColorPicker-ball"></i>
</div>
<div class="ColorPicker-colorWrapper">
<div class="ColorPicker-color colorpicker-color js-color"></div>
</div>
</div>

View File

@@ -0,0 +1,179 @@
var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var template = require('./template.tpl');
var colorPickerTemplate = require('./color-picker-template.tpl');
var Utils = require('builder/helpers/utils');
require('bootstrap-colorpicker');
var ESCAPE_KEY_CODE = 27;
module.exports = CoreView.extend({
events: {
'blur .js-hex': '_onChangeHex',
'blur .js-inputColor': '_onChangeColorValue',
'blur .js-a': '_onChangeOpacity'
},
initialize: function (opts) {
var opacity = opts.opacity != null ? opts.opacity : 1;
this.model = new Backbone.Model({
hex: opts.value,
opacity: opacity
});
this._onEscape = this._onEscapePressed.bind(this);
this._initBinds();
},
render: function () {
var hex = this.model.get('hex');
var rgb = Utils.hexToRGB(this._sanitizeHex());
var color = _.extend(rgb, {
hex,
opacity: this.model.get('opacity'),
opacityDisabled: this.options.disableOpacity
});
this.$el.append(template(color));
var rgbaTemplate = _.template('rgba(<%- r %>, <%- g %>, <%- b %>, <%- opacity %>)');
this.$('.js-colorPicker').colorpicker({
color: rgbaTemplate(color),
format: 'rgba',
container: true,
horizontal: true,
inline: true,
customClass: 'ColorPicker',
template: colorPickerTemplate(),
slidersHorz: {
saturation: {
maxLeft: 206,
maxTop: 95,
callLeft: 'setSaturation',
callTop: 'setBrightness'
},
hue: {
maxLeft: 180,
maxTop: 0,
callLeft: 'setHue',
callTop: false
},
alpha: {
maxLeft: 180,
maxTop: 0,
callLeft: 'setAlpha',
callTop: false
}
}
});
if (this.options.disableOpacity) {
this.$('.js-colorPicker .js-alpha').addClass('is-hidden');
}
var setNewColor = function (e) {
var rgb = e.color.toRGB();
var hex = e.color.toHex();
this.model.set({ hex: hex, r: rgb.r, g: rgb.g, b: rgb.b, opacity: rgb.a });
this.$('.js-color').css('opacity', rgb.a);
}.bind(this);
this.$('.js-colorPicker').colorpicker().on('changeColor', setNewColor);
this._initDocumentBinds();
return this;
},
_initBinds: function () {
this.model.bind('change:hex', this._onChangeColor, this);
this.model.bind('change:r', this._onChangeColor, this);
this.model.bind('change:g', this._onChangeColor, this);
this.model.bind('change:b', this._onChangeColor, this);
this.model.bind('change:opacity', this._onChangeColor, this);
},
_onEscapePressed: function (ev) {
if (ev.which === ESCAPE_KEY_CODE) {
this.remove();
}
},
_initDocumentBinds: function () {
$(document).on('keydown', this._onEscape);
},
_destroyDocumentBinds: function () {
$(document).off('keydown', this._onEscape);
},
_sanitizeHex: function () {
return Utils.sanitizeHex(this.model.get('hex'));
},
_onChangeColor: function () {
this.trigger('change', {
opacity: this.model.get('opacity'),
hex: this.model.get('hex')
}, this);
this.$('.js-hex').val(this.model.get('hex'));
this.$('.js-r').val(this.model.get('r'));
this.$('.js-g').val(this.model.get('g'));
this.$('.js-b').val(this.model.get('b'));
this.$('.js-a').val(this.model.get('opacity'));
},
_onChangeOpacity: function () {
var opacity = +this.$('.js-a').val();
if (_.isNumber(opacity) && opacity >= 0 && opacity <= 1) {
this.setColor(this.model.get('hex'), opacity);
}
},
_onChangeColorValue: function (e) {
this.$(e.target).removeClass('has-error');
var r = +this.$('.js-r').val();
var g = +this.$('.js-g').val();
var b = +this.$('.js-b').val();
var hex = Utils.rgbToHex(r, g, b);
if (Utils.isValidHex(hex)) {
this.setColor(hex);
} else {
this.$(e.target).addClass('has-error');
}
},
_onChangeHex: function (e) {
this.$(e.target).removeClass('has-error');
var hex = this.$('.js-hex').val();
if (Utils.isValidHex(hex)) {
this.setColor(hex);
} else {
this.$(e.target).addClass('has-error');
}
},
setColor: function (hex, opacity) {
if (_.isUndefined(opacity)) {
opacity = this.model.get('opacity') || 1;
}
this.$('.js-colorPicker').colorpicker('setValue', Utils.hexToRGBA(hex, opacity));
},
clean: function () {
this._destroyDocumentBinds();
this.trigger('onClean', this);
CoreView.prototype.clean.apply(this);
}
});

View File

@@ -0,0 +1,25 @@
<div class="Editor-boxModalContent">
<div class="ColorPicker-pickerWrapper js-colorPicker"></div>
<div class="ColorPicker-inputs">
<div class="ColorPicker-inputWrapper CDB-Text">
<input type="text" class="CDB-Text CDB-InputText ColorPicker-input is-color js-hex" value="<%- hex %>"/>
<span class="ColorPicker-inputLabel u-upperCase">HEX</span>
</div>
<div class="ColorPicker-inputWrapper CDB-Text">
<input type="text" class="CDB-Text CDB-InputText ColorPicker-input js-inputColor js-r" value="<%- r %>"/>
<span class="ColorPicker-inputLabel u-upperCase">R</span>
</div>
<div class="ColorPicker-inputWrapper CDB-Text">
<input type="text" class="CDB-Text CDB-InputText ColorPicker-input js-inputColor js-g" value="<%- g %>" />
<span class="ColorPicker-inputLabel u-upperCase">G</span>
</div>
<div class="ColorPicker-inputWrapper CDB-Text">
<input type="text" class="CDB-Text CDB-InputText ColorPicker-input js-inputColor js-b" value="<%- b %>" />
<span class="ColorPicker-inputLabel u-upperCase">B</span>
</div>
<div class="ColorPicker-inputWrapper CDB-Text">
<input type="text" class="CDB-Text CDB-InputText ColorPicker-input js-a<% if (opacityDisabled) { %> is-disabled<% } %>" value="<%- opacity %>" <% if (opacityDisabled) { %>disabled<% } %> />
<span class="ColorPicker-inputLabel u-upperCase">A</span>
</div>
</div>
</div>

View File

@@ -0,0 +1,111 @@
var CoreView = require('backbone/core-view');
var ContextMenuView = require('./context-menu/context-menu-view');
var CustomListCollection = require('./custom-list/custom-list-collection');
var template = require('./context-menu-factory.tpl');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var TipsyTooltipView = require('./tipsy-tooltip-view');
var REQUIRED_OPTS = [
'menuItems'
];
module.exports = CoreView.extend({
events: {
'click .js-toggle-menu': '_onToggleContextMenuClicked'
},
className: 'CDB-Shape',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this.template = opts.template || template;
this._initContextMenu(this._menuItems);
},
render: function () {
this.clearSubViews();
this.$el.html(this.template());
this._initViews();
return this;
},
_initViews: function () {
var tooltip = new TipsyTooltipView({
el: this.$el,
title: function () {
return _t('more-options');
},
gravity: 'w'
});
this.addView(tooltip);
},
_initContextMenu: function (items) {
this._menuItems = new CustomListCollection(items);
this._menuItems.on('change:selected', this._onContextMenuSelect, this);
this.add_related_model(this._menuItems);
},
_onContextMenuSelect: function (menuItem) {
var action = menuItem.get('action');
action && action.call(this);
},
_showContextMenu: function (position) {
var triggerElementID = 'context-menu-trigger-' + this.cid;
this.$('.js-toggle-menu').attr('id', triggerElementID);
var menuItems = this._menuItems;
this._resetContextMenuItems();
this._menuView = new ContextMenuView({
collection: menuItems,
triggerElementID: triggerElementID,
position: position
});
this._menuView.model.on('change:visible', function (model, isContextMenuVisible) {
if (this._hasContextMenu() && !isContextMenuVisible) {
this._hideContextMenu();
}
}, this);
this._menuView.show();
this.addView(this._menuView);
},
_resetContextMenuItems: function () {
var selected = this._menuItems.getSelectedItem();
selected && selected.set({selected: false}, {silent: true});
},
_hasContextMenu: function () {
return this._menuView != null;
},
_hideContextMenu: function () {
this.removeView(this._menuView);
this._menuView.clean();
delete this._menuView;
},
_onToggleContextMenuClicked: function (event) {
event.preventDefault();
event.stopPropagation();
if (this._hasContextMenu()) {
this._hideContextMenu();
} else {
this._showContextMenu({
x: event.pageX,
y: event.pageY
});
}
},
getContextMenu: function () {
return this._menuView;
}
});

View File

@@ -0,0 +1,5 @@
<button class="CDB-Shape-threePoints is-blue is-small js-toggle-menu">
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
</button>

View File

@@ -0,0 +1,139 @@
var $ = require('jquery');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var CustomListView = require('builder/components/custom-list/custom-list-view');
var CustomListItemView = require('builder/components/custom-list/custom-list-item-view');
var itemTemplate = require('builder/components/custom-list/custom-list-item.tpl');
var magicPositioner = require('builder/helpers/magic-positioner');
var DropdownOverlayView = require('builder/components/dropdown-overlay/dropdown-overlay-view');
var ESCAPE_KEY_CODE = 27;
/*
* A context menu
*/
module.exports = CoreView.extend({
options: {
position: {
x: 0,
y: 0
},
offset: {
x: 0,
y: 15
},
itemTemplate: itemTemplate,
itemView: CustomListItemView
},
className: 'CDB-Box-modal CDB-SelectItem CustomList CustomList--small',
tagName: 'div',
initialize: function (opts) {
opts = opts || {};
if (!opts.collection) throw new Error('collection option is required');
if (!opts.triggerElementID) throw new Error('element id is required');
this._triggerElementID = opts.triggerElementID;
this.model = new Backbone.Model({
visible: false
});
this._onEscapePressed = this._onEscapePressed.bind(this);
this._initBinds();
},
_initBinds: function () {
this.model.on('change:visible', function (mdl, isVisible) {
this.render();
if (isVisible) {
this._initDocumentBinds();
} else {
this._destroyDocumentBinds();
}
}, this);
this.collection.on('change:selected', this.hide, this);
this.add_related_model(this.collection);
},
_initDocumentBinds: function () {
$(document).on('keydown', this._onEscapePressed);
},
_destroyDocumentBinds: function () {
$(document).off('keydown', this._onEscapePressed);
},
render: function () {
var $body = $('body');
var posX = (this.options.position.x || 0) + this.options.offset.x;
var posY = (this.options.position.y || 0) + this.options.offset.y;
this.clearSubViews();
this.$el.empty();
this._renderList();
this.$el.toggle(this.isVisible());
$body.append(this.el);
this.$el.css(
magicPositioner({
parentView: $body,
posX: posX,
posY: posY
})
);
this.dropdownOverlay = new DropdownOverlayView({
visible: this.isVisible(),
onClickAction: this.hide.bind(this)
});
this.addView(this.dropdownOverlay);
return this;
},
_onEscapePressed: function (ev) {
if (ev.which === ESCAPE_KEY_CODE) {
this.hide();
}
},
_renderList: function () {
this._listView = new CustomListView({
model: this.model,
collection: this.collection,
typeLabel: '',
itemView: this.options.itemView,
itemTemplate: this.options.itemTemplate,
size: 5
});
this.$el.append(this._listView.render().el);
this.addView(this._listView);
},
show: function () {
this.model.set('visible', true);
},
hide: function () {
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
isVisible: function () {
return this.model.get('visible');
},
clean: function () {
this._destroyDocumentBinds();
CoreView.prototype.clean.apply(this);
}
});

View File

@@ -0,0 +1,54 @@
var Backbone = require('backbone');
var CustomCarouselModel = require('./custom-carousel-item-model');
/*
* Custom list collection, it parses pairs like:
*
* [{ val, label }]
* ["string"]
*/
module.exports = Backbone.Collection.extend({
model: function (attrs, opts) {
var d = {};
if (typeof attrs === 'string') {
d.val = attrs;
} else {
d = attrs;
}
return new CustomCarouselModel(d);
},
initialize: function () {
this._initBinds();
},
_initBinds: function () {
this.bind('change:selected', this._onSelectedChange, this);
},
_onSelectedChange: function (changedModel, isSelected) {
if (isSelected) {
this.each(function (m) {
if (m.cid !== changedModel.cid && m.get('selected')) {
m.set('selected', false);
}
});
}
},
getSelected: function () {
return this.findWhere({ selected: true });
},
getSelectedValue: function () {
var selectedModel = this.getSelected();
return selectedModel && selectedModel.get('val');
},
getHighlighted: function () {
return this.findWhere({ highlighted: true });
}
});

View File

@@ -0,0 +1,26 @@
var Backbone = require('backbone');
/*
* List item model
*
*/
module.exports = Backbone.Model.extend({
defaults: {
selected: false,
label: '',
template: function () {
return '';
}
},
getName: function () {
return this.get('label') || this.getValue();
},
getValue: function () {
return this.get('val');
}
});

View File

@@ -0,0 +1,77 @@
var CoreView = require('backbone/core-view');
var template = require('./custom-carousel-item.tpl');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
module.exports = CoreView.extend({
className: 'Carousel-item',
tagName: 'li',
events: {
'mouseenter': '_onMouseEnter',
'mouseleave': '_onMouseLeave',
'click': '_onClick'
},
initialize: function (opts) {
this._itemClassName = opts && opts.itemOptions ? opts.itemOptions.className : '';
this._initBinds();
},
render: function () {
// NOTE: this function calls destroyTipsy for each tooltip, which calls:
// this.$el.unbind('mouseleave mouseenter');
// So we need to bind the events again after calling clearSubViews
this.clearSubViews();
this.$el.bind('mouseenter', this._onMouseEnter.bind(this));
this.$el.bind('mouseleave', this._onMouseLeave.bind(this));
this.$el.html(
template({
name: this.model.getName(),
className: this._itemClassName,
template: this.model.get('template')()
})
);
if (this.model.getValue()) {
this.$el.addClass('js-' + this.model.getValue());
}
this.$el.toggleClass('is-selected', !!this.model.get('selected'));
this._initViews();
return this;
},
_initViews: function () {
if (this.model.get('tooltip')) {
var tooltip = new TipsyTooltipView({
el: this.$el,
gravity: 's',
title: function () {
return this.model.get('tooltip');
}.bind(this)
});
this.addView(tooltip);
}
},
_initBinds: function () {
this.model.bind('change:selected', this.render, this);
},
_onMouseEnter: function () {
this.model.set('highlighted', true);
},
_onMouseLeave: function () {
this.model.set('highlighted', false);
},
_onClick: function (e) {
this.killEvent(e);
this.model.set('selected', true);
}
});

View File

@@ -0,0 +1,3 @@
<button<% if (className) { %> class="<%- className %>"<% } %>>
<%= template %>
</button>

View File

@@ -0,0 +1,137 @@
var CoreView = require('backbone/core-view');
var Ps = require('perfect-scrollbar');
var template = require('./custom-carousel.tpl');
var CarouselCollection = require('./custom-carousel-collection');
var CarouselItemView = require('./custom-carousel-item-view');
var _ = require('underscore');
/*
* A custom carousel selector
*
* It accepts a collection of (val, label) model attributes or a values array
* with the same content or only strings.
*
* new CustomCarousel({
* options: [
* {
* val: 'hello',
* label: 'hi'
* }
* ]
* });
*/
module.exports = CoreView.extend({
className: 'Carousel',
tagName: 'div',
initialize: function (opts) {
if (!opts.collection) {
if (!opts.options) { throw new Error('options array {value, label} is required'); }
this.collection = new CarouselCollection(opts.options);
this.options = opts;
}
this._bindedCheckShadows = this._checkShadows.bind(this);
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.html(template());
this._renderList();
this._applyCustomScroll();
return this;
},
_initBinds: function () {
this.collection.bind('change:selected', this._checkScroll, this);
},
_renderList: function () {
this.collection.each(function (model) {
this._createItem(model);
}, this);
},
_createItem: function (model) {
var opts = this.options;
var className = opts && opts.listItemOptions ? opts.listItemOptions.className : 'Carousel-item';
var view = new CarouselItemView({
className: className,
model: model,
itemOptions: this.options.itemOptions
});
this._listContainer().append(view.render().el);
this.addView(view);
},
_applyCustomScroll: function () {
Ps.initialize(this._listContainer().get(0), {
wheelSpeed: 1,
wheelPropagation: false,
swipePropagation: true,
suppressScrollY: true,
stopPropagationOnClick: false,
minScrollbarLength: 120,
useBothWheelAxes: true
});
this._checkScroll();
this._bindScroll();
},
_destroyCustomScroll: function () {
this._unbindScroll();
Ps.destroy(this._listContainer().get(0));
},
_bindScroll: function () {
this._listContainer()
.on('ps-x-reach-start', this._bindedCheckShadows)
.on('ps-x-reach-end', this._bindedCheckShadows)
.on('ps-scroll-x', this._bindedCheckShadows);
},
_unbindScroll: function () {
this._listContainer()
.off('ps-x-reach-start', this._bindedCheckShadows)
.off('ps-x-reach-end', this._bindedCheckShadows)
.off('ps-scroll-x', this._bindedCheckShadows);
},
_checkScroll: function () {
var position = this.$('.is-selected').position();
if (position) {
this._listContainer().scrollLeft(position.left - 20);
this._bindedCheckShadows();
}
},
_listContainer: function () {
return this.$('.js-list');
},
_checkShadows: function () {
var currentPos = this._listContainer().scrollLeft();
var max = this._listContainer().get(0).scrollWidth;
var width = this._listContainer().outerWidth();
var maxPos = max - width;
this.$('.js-leftShadow').toggleClass('is-visible', currentPos > 0);
this.$('.js-rightShadow').toggleClass('is-visible', currentPos < maxPos);
},
clean: function () {
this._destroyCustomScroll();
CoreView.prototype.clean.apply(this);
},
initScroll: function () {
setTimeout(_.bind(function () {
this._checkShadows();
this._checkScroll();
Ps.update(this._listContainer().get(0));
}, this), 0);
}
});

View File

@@ -0,0 +1,3 @@
<div class="Carousel-shadow Carousel-shadow--left js-leftShadow"></div>
<div class="Carousel-shadow Carousel-shadow--right is-visible js-rightShadow"></div>
<ul class="Carousel-list js-list"></ul>

View File

@@ -0,0 +1,3 @@
<div class="CDB-ListDecoration-itemLink u-flex u-justifySpace u-alignCenter u-actionTextColor <% if (isSelected) { %> is-selected <% } %>">
<%- _t('form-components.editors.fill.quantification.methods.' + name) %>
</div>

View File

@@ -0,0 +1,63 @@
var CoreView = require('backbone/core-view');
var CustomListView = require('builder/components/custom-list/custom-view');
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
var ColumnListViewTemplate = require('builder/components/custom-list/column-list/column-list-view.tpl');
module.exports = CoreView.extend({
defaults: {
headerTitle: ''
},
events: {
'click .js-back': '_onClickBack'
},
initialize: function (opts) {
if (!opts.stackLayoutModel) throw new Error('stackLayoutModel is required');
if (!opts.columns) throw new Error('columns param is required');
this._stackLayoutModel = opts.stackLayoutModel;
this._columns = opts.columns;
this._showSearch = opts.showSearch || false;
this._typeLabel = opts.typeLabel;
this._itemTemplate = opts.itemTemplate;
this.collection = new CustomListCollection(this._columns);
this.listenTo(this.collection, 'change:selected', this._onSelectItem);
},
render: function () {
this.clearSubViews();
this.$el.empty();
this.$el.append(
ColumnListViewTemplate({
headerTitle: this.options.headerTitle
})
);
this._initViews();
return this;
},
_onClickBack: function (e) {
this.killEvent(e);
this.trigger('back', this);
},
_initViews: function () {
this._listView = new CustomListView({
collection: this.collection,
showSearch: this._showSearch,
typeLabel: this._typeLabel,
itemTemplate: this._itemTemplate
});
this.$('.js-content').append(this._listView.render().$el);
this.addView(this._listView);
},
_onSelectItem: function (item) {
this.trigger('selectItem', item, this);
}
});

View File

@@ -0,0 +1,13 @@
<% if (headerTitle) { %>
<div class="CDB-Box-modalHeader">
<ul class="CDB-Box-modalHeaderItem CDB-Box-modalHeaderItem--block CDB-Box-modalHeaderItem--paddingHorizontal">
<li class="CDB-ListDecoration-item CDB-ListDecoration-itemPadding--vertical CDB-Text CDB-Size-medium u-secondaryTextColor">
<button class="u-actionTextColor js-back u-rSpace">
<i class="CDB-IconFont CDB-IconFont-arrowPrev Size-large"></i>
</button>
<%- headerTitle%>
</li>
</ul>
</div>
<% } %>
<div class="js-content"></div>

View File

@@ -0,0 +1,32 @@
var CoreView = require('backbone/core-view');
var template = require('./custom-list-action.tpl');
module.exports = CoreView.extend({
className: 'CDB-Text CDB-Size-small is-semibold u-upperCase u-actionTextColor u-lSpace--xl',
tagName: 'button',
events: {
'click': '_onClick'
},
initialize: function (opts) {
this.options = opts;
},
render: function () {
this.$el.empty();
this.clearSubViews();
this.$el.append(template({
label: this.options.label
}));
return this;
},
_onClick: function () {
this.options.action && this.options.action();
}
});

View File

@@ -0,0 +1 @@
<%- label %>

View File

@@ -0,0 +1,6 @@
<div class="CDB-ListDecoration-item CustomList-item js-listItem">
<button type="button" class="CDB-ListDecoration-itemLink CustomList--full js-add-custom-value u-ellipsis" data-val="<%- query %>" title="<%- query %>">
<p class="CDB-Text CDB-FontSize-small u-altTextColor"><%- _t('components.custom-list.add-custom-result') %></p>
“<%- query %>”
</button>
</div>

View File

@@ -0,0 +1,122 @@
var Backbone = require('backbone');
var CustomListItemModel = require('./custom-list-item-model');
var _ = require('underscore');
/*
* Custom list collection, it parses pairs like:
*
* [{ val, label }]
* ["string"]
*/
module.exports = Backbone.Collection.extend({
sort_key: 'id', // default sort key
initialize: function (models, options) {
this.options = _.extend({ silent: true }, options);
this._initBinds();
},
comparator: function (item) {
var name = item.get(this.sort_key);
return this._nameToLowerCase(name);
},
model: function (attrs, opts) {
var d = {};
if (typeof attrs === 'string') {
d.val = attrs;
} else {
d = attrs;
}
return new CustomListItemModel(d, opts);
},
sortByKey: function (key) {
this.sort_key = key;
this.sort();
},
_initBinds: function () {
this.bind('change:selected', this._onSelectedChange, this);
},
_nameToLowerCase: function (name) {
/*
* It could still be evaluated like true if it is a boolean/number
* in that case, we convert it to a string
*/
if (_.isUndefined(name) || _.isNull(name)) return name;
return _.isString(name) ? name.toLowerCase() : name.toString().toLowerCase();
},
search: function (query) {
if (!query) return this;
query = query.toLowerCase();
return _(this.filter(function (model) {
var name = model.getName();
var val = this._nameToLowerCase(name);
return val ? ~val.indexOf(query) : -1;
}, this));
},
_onSelectedChange: function (changedModel, isSelected) {
if (isSelected) {
this.each(function (m) {
if (m.cid !== changedModel.cid) {
m.set({
selected: false
}, {
silent: this.options.silent
});
}
}, this);
}
},
getSelectedItem: function () {
return this.findWhere({ selected: true });
},
containsValue: function (value) {
return this.find(function (mdl) {
return mdl.getValue() === value;
});
},
setSelected: function (value) {
var selectedModel;
var silent = { silent: this.options.silent };
this.each(function (mdl) {
if (mdl.getValue() === value) {
mdl.set({
selected: true
}, silent);
selectedModel = mdl;
} else {
mdl.set({
selected: false
}, silent);
}
});
return selectedModel;
},
removeSelected: function () {
this.each(function (mdl) {
mdl.set({
selected: false
});
});
},
isAsync: function () {
return false;
}
});

View File

@@ -0,0 +1,9 @@
<div class="CustomList-message">
<p class="CustomList-messageText CDB-Text CDB-Size-medium u-secondaryTextColor">
<% if (query && query.length) { %>
<%- _t('components.custom-list.no-results', { typeLabel: typeLabel, query: query }) %>
<% } else { %>
<%- _t('components.custom-list.no-items', { typeLabel: typeLabel }) %>
<% } %>
</p>
</div>

View File

@@ -0,0 +1,5 @@
<div class="CDB-Box-modalHeader">
<div class="CDB-Box-modalHeaderItem js-header u-alignCenter u-justifySpace">
<div class="u-flex js-actions"></div>
</div>
</div>

View File

@@ -0,0 +1,22 @@
var Backbone = require('backbone');
/*
* List item model
*
*/
module.exports = Backbone.Model.extend({
defaults: {
selected: false
},
getName: function () {
return (this.get('label') == null) ? this.getValue() : this.get('label'); // eslint-disable-line
},
getValue: function () {
return this.get('val');
}
});

View File

@@ -0,0 +1,66 @@
var _ = require('underscore');
var CoreView = require('backbone/core-view');
module.exports = CoreView.extend({
options: {
template: require('./custom-list-item.tpl')
},
className: 'CDB-ListDecoration-item CustomList-item js-listItem',
tagName: 'li',
events: {
'mouseenter': '_onMouseEnter',
'mouseleave': '_onMouseLeave',
'click': '_onClick'
},
initialize: function (opts) {
this.options = _.extend({}, this.options, opts);
this.model.on('change', this.render, this);
},
render: function () {
this.$el.empty();
this.clearSubViews();
var name = this.model.getName() == null ? 'null' : this.model.getName();
this.$el.append(
this.options.template(
_.extend({
typeLabel: this.options.typeLabel,
isSelected: this.model.get('selected'),
isDisabled: this.model.get('disabled'),
isDestructive: this.model.get('destructive'),
name: name,
val: this.model.getValue(),
options: this.model.get('renderOptions')
})
)
);
this.$el
.attr('data-val', this.model.getValue())
.toggleClass('is-disabled', !!this.model.get('disabled'));
return this;
},
_onMouseLeave: function () {
this.$el.removeClass('is-highlighted');
},
_onMouseEnter: function () {
this.$el.addClass('is-highlighted');
},
_onClick: function (ev) {
this.killEvent(ev);
this.model.set({
selectedClass: ev.target.classList,
selected: true
});
}
});

View File

@@ -0,0 +1,17 @@
<button type="button" class="CDB-ListDecoration-itemLink u-ellipsis
<% if (isSelected) { %> is-selected <% } %>
<% if (isDestructive) { %>
u-errorTextColor
<% } else if (isDisabled) { %>
u-hintTextColor
<% } else { %>
u-actionTextColor
<% } %>
" title="<%- name %>">
<div class="u-iBlock u-rSpace">
<input class="CDB-Checkbox js-input" type="checkbox" name="" value="" <% if (isSelected) { %>checked<% } %> <% if (isDisabled) { %>disabled<% } %> />
<span class="u-iBlock CDB-Checkbox-face"></span>
</div>
<%- name %>
</button>

View File

@@ -0,0 +1,12 @@
<button type="button" class="CDB-ListDecoration-itemLink u-ellipsis
<% if (isSelected) { %> is-selected <% } %>
<% if (isDestructive) { %>
u-errorTextColor
<% } else if (isDisabled) { %>
u-hintTextColor
<% } else { %>
u-actionTextColor
<% } %>
" title="<%- name %>">
<%- name %>
</button>

View File

@@ -0,0 +1,40 @@
var _ = require('underscore');
var CustomListCollection = require('./custom-list-collection');
module.exports = CustomListCollection.extend({
_initBinds: function () { },
setSelected: function (value) {
var selectedModel;
var silentTrue = { silent: true };
if (_.isArray(value)) {
this.each(function (mdl) {
if (_.contains(value, mdl.getValue())) {
mdl.set({
selected: true
}, silentTrue);
selectedModel = mdl;
} else {
mdl.set({
selected: false
}, silentTrue);
}
});
} else {
this.each(function (mdl) {
if (mdl.getValue() === value) {
mdl.set({
selected: true
}, silentTrue);
selectedModel = mdl;
} else {
mdl.set({
selected: false
}, silentTrue);
}
});
}
return selectedModel;
}
});

View File

@@ -0,0 +1,8 @@
var CustomListItemView = require('./custom-list-item-view');
module.exports = CustomListItemView.extend({
_onClick: function (ev) {
this.killEvent(ev);
this.model.set('selected', !this.model.get('selected'));
}
});

View File

@@ -0,0 +1,62 @@
var CoreView = require('backbone/core-view');
var template = require('./custom-list-search.tpl');
module.exports = CoreView.extend({
className: 'CustomList--full',
tagName: 'form',
events: {
'keyup .js-search': '_onSearchType',
'click .js-clear': '_onClickClear',
'submit': '_submit'
},
initialize: function () {
this.template = this.options.template || template;
this.searchPlaceholder = this.options.searchPlaceholder || _t('components.custom-list.placeholder', { typeLabel: this.options.typeLabel });
this._initBinds();
},
render: function () {
this.$el
.empty()
.append(
this.template({
query: this.model.get('query'),
searchPlaceholder: this.searchPlaceholder
})
);
return this;
},
_initBinds: function () {
this.model.on('change:query', this._checkButtons, this);
},
_checkButtons: function () {
var query = this.model.get('query');
this.$('.js-clear').toggleClass('u-transparent', query.length === 0);
},
_onSearchType: function (e) {
this._submit();
},
_onClickClear: function (e) {
e.stopPropagation();
this.model.set('query', '');
this.render();
this.focus();
},
focus: function () {
this.$('.js-search').focus();
},
_submit: function (ev) {
this.killEvent(ev);
var query = this.$('.js-search').val();
this.model.set('query', query);
}
});

View File

@@ -0,0 +1,8 @@
<div class="u-flex">
<input type="text" name="text" autocomplete="off" value="<%- query %>" placeholder="<%- searchPlaceholder %>" class="CDB-InputTextPlain CDB-Text js-search">
<button type="button" class="u-lSpace--xl u-transparent js-clear">
<div class="CDB-Shape">
<div class="CDB-Shape-close is-blue is-large"></div>
</div>
</button>
</div>

View File

@@ -0,0 +1,234 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var Ps = require('perfect-scrollbar');
var emptyTemplate = require('./custom-list-empty.tpl');
var addTemplate = require('./custom-list-add.tpl');
var template = require('./custom-list.tpl');
var Utils = require('builder/helpers/utils');
var ARROW_DOWN_KEY_CODE = 40;
var ARROW_UP_KEY_CODE = 38;
var ENTER_KEY_CODE = 13;
module.exports = CoreView.extend({
module: 'components:custom-list:custom-list-view',
options: {
size: 3
},
className: 'CDB-Text CDB-Size-medium CustomList-listWrapper',
tagName: 'div',
events: {
'click .js-add-custom-value': '_onClickAddCustomValue',
'mouseover': '_onMouseOver',
'mouseout': '_onMouseOut'
},
initialize: function (opts) {
this.options = _.extend({}, this.options, opts);
this._onKeyDownBinded = this._onKeyDown.bind(this);
this._needsMaxSize = true;
if (this.options.mouseOverAction) {
this._mouseOverAction = this.options.mouseOverAction;
}
if (this.options.mouseOutAction) {
this._mouseOutAction = this.options.mouseOutAction;
}
this._initBinds();
},
render: function () {
this.clearSubViews();
this._removeArrowBinds();
this._destroyCustomScroll();
this.$el.empty();
var query = this.model.get('query');
var items = this.collection.search(query);
this.$el.append(template());
var allowFreeTextInput = this.options.allowFreeTextInput;
if (allowFreeTextInput && query && !Utils.isBlank(query)) {
if (!this.collection.containsValue(query)) {
this.$el.prepend(
addTemplate({
query: query,
typeLabel: this.options.typeLabel
})
);
}
}
if (items.size() > 0) {
items.each(this._renderItem, this);
this._applyArrowBinds();
// Perfect-scroll needs to have the element in the DOM in order to
// style/positionate the scroll properly, small trick
setTimeout(this._applyCustomScroll.bind(this), 0);
} else if (!allowFreeTextInput || Utils.isBlank(query)) {
this.$el.append(
emptyTemplate({
query: query,
typeLabel: this.options.typeLabel
})
);
}
return this;
},
_renderItem: function (model) {
if (model.get('hidden')) return;
var ItemViewClass = this.options.itemView;
var itemView = new ItemViewClass({
model: model,
typeLabel: this.options.typeLabel,
template: this.options.itemTemplate
});
this.$('.js-list').append(itemView.render().el);
this.addView(itemView);
itemView.bind('customEvent', function (eventName, item) {
this.trigger('customEvent', eventName, item, this);
}, this);
},
_initBinds: function () {
this.model.bind('change:query', this._onQueryChanged, this);
},
_applyArrowBinds: function () {
document.addEventListener('keydown', this._onKeyDownBinded);
},
_removeArrowBinds: function () {
document.removeEventListener('keydown', this._onKeyDownBinded);
},
_getSelected: function () {
var selectedModel = this.collection.getSelectedItem();
var selectedValue;
if (selectedModel) {
selectedValue = selectedModel.getValue();
return this.$("[data-val='" + selectedValue + "']");
}
return null;
},
_highlightSelected: function (item) {
var itemHeight = item.outerHeight();
item.addClass('is-highlighted');
this.$('.js-list').scrollTop(item.index() * itemHeight);
},
_onClickAddCustomValue: function (event) {
this.killEvent(event);
var query = this.model.get('query');
var model = this.collection.add({
val: query,
label: '“' + query + '”',
dirty: true
});
this.collection.sortByKey('val');
model.set('selected', true);
},
_onKeyDown: function (event) {
if (this.model.get('visible') === false) {
return;
}
var key = event.which;
var $listItems = this.$('.js-listItem');
var $highlighted = $listItems.filter('.is-highlighted');
var $current;
var model;
$highlighted.removeClass('is-highlighted');
if (key === ARROW_DOWN_KEY_CODE) {
if (!$highlighted.length || $highlighted[0] === $listItems.last()[0]) {
$current = $listItems.eq(0);
} else {
$current = $highlighted.next();
}
} else if (key === ARROW_UP_KEY_CODE) {
if (!$highlighted.length || $highlighted[0] === $listItems.first()[0]) {
$current = $listItems.last();
} else {
$current = $highlighted.prev();
}
} else if (key === ENTER_KEY_CODE) {
event.preventDefault();
if ($highlighted && $highlighted.length) {
model = _.first(this.collection.where({ val: $highlighted.data('val') }));
if (model) {
model.set('selected', true);
}
return false;
}
}
$current && this._highlightSelected($current);
},
_onQueryChanged: function () {
var prevQuery = this.collection.findWhere({ val: this.model.previous('query'), dirty: true });
this.collection.remove(prevQuery);
this.render();
},
_applyCustomScroll: function () {
Ps.initialize(this._wrapperContainer().get(0), {
wheelSpeed: 2,
wheelPropagation: true,
stopPropagationOnClick: false,
minScrollbarLength: 20
});
},
_destroyCustomScroll: function () {
if (this._wrapperContainer().length > 0) {
Ps.destroy(this._wrapperContainer().get(0));
}
},
_wrapperContainer: function () {
return this.$('.js-list');
},
clean: function () {
this._removeArrowBinds();
this._destroyCustomScroll();
CoreView.prototype.clean.apply(this);
},
highlight: function () {
var selected = this._getSelected();
selected && this._highlightSelected(selected);
},
_onMouseOver: function () {
this._mouseOverAction && this._mouseOverAction();
},
_onMouseOut: function () {
this._mouseOutAction && this._mouseOutAction();
}
});

View File

@@ -0,0 +1 @@
<ul class="CustomList-list js-list"></ul>

View File

@@ -0,0 +1,221 @@
var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var CustomListCollection = require('./custom-list-collection');
var SearchView = require('./custom-list-search-view');
var CustomListView = require('./custom-list-view');
var headerTemplate = require('./custom-list-header.tpl');
var CustomListAction = require('./custom-list-action-view');
var itemTemplate = require('./custom-list-item.tpl');
var CustomListItemView = require('./custom-list-item-view');
/*
* A custom list with possibility to search within values.
*
* It accepts a collection of (val, label) model attributes or a values array
* with the same content or only strings.
*
* new CustomList({
* showSearch: false,
* itemTemplate: itemTemplate,
* values: [
* {
* val: 'hello',
* label: 'hi'
* }
* ]
* });
*/
module.exports = CoreView.extend({
module: 'components:custom-list:custom-view',
options: {
showSearch: true,
allowFreeTextInput: false,
typeLabel: 'column',
itemTemplate: itemTemplate,
itemView: CustomListItemView
},
className: 'CDB-Box-modal CustomList',
tagName: 'div',
events: {
'mouseover': '_onMouseOver',
'mouseout': '_onMouseOut'
},
initialize: function (opts) {
if (!opts.collection) {
if (!opts.options) { throw new Error('options array {value, label} is required'); }
this.collection = new CustomListCollection(opts.options);
}
if (opts.position) {
this.$el.css(opts.position);
}
this.options = _.extend({}, this.options, opts);
this._selectModel = this.options.selectModel;
if (this.options.mouseOverAction) {
this._mouseOverAction = this.options.mouseOverAction;
}
if (this.options.mouseOutAction) {
this._mouseOutAction = this.options.mouseOutAction;
}
this.model = new Backbone.Model({
query: '',
visible: false
});
this._initBinds();
},
render: function () {
this.$el.empty();
this.clearSubViews();
if (this.options.showSearch || this.options.actions) {
this._renderHeader();
}
if (this.options.showSearch) {
this._renderSearch();
}
if (this.options.actions) {
this._renderActions();
}
this._renderList();
if (this.options.showSearch) {
this._focusSearch();
}
return this;
},
_initBinds: function () {
this.model.bind('change:visible', this._onVisibleChanged, this);
this.model.bind('change:query', this._setActionsVisibility, this);
},
_renderHeader: function () {
this.$el.prepend(headerTemplate());
},
_renderSearch: function () {
this._searchView = new SearchView({
template: this.options.searchTemplate,
typeLabel: this.options.typeLabel,
searchPlaceholder: this.options.searchPlaceholder,
model: this.model
});
this.$('.js-header').prepend(this._searchView.render().el);
this.addView(this._searchView);
},
_renderActions: function () {
_.each(this.options.actions, function (action) {
var view = new CustomListAction(action);
this.$('.js-actions').append(view.render().el);
this.addView(view);
}, this);
},
_setActionsVisibility: function () {
this.$('.js-actions').toggleClass('u-hide', this.model.get('query') !== '');
},
_focusSearch: function () {
setTimeout(function () {
if (this._searchView) {
this._searchView.focus();
var input = this._searchView.$('input');
var $initialVal = input.val();
input.val($initialVal + ' ');
input.val($initialVal);
}
}.bind(this), 0);
},
_renderList: function () {
this._listView = new CustomListView({
model: this.model,
allowFreeTextInput: this.options.allowFreeTextInput,
collection: this.collection,
typeLabel: this.options.typeLabel,
itemView: this.options.itemView,
itemTemplate: this.options.itemTemplate,
size: this.options.size,
mouseOverAction: this._mouseOverAction,
mouseOutAction: this._mouseOutAction
});
this.$el.append(this._listView.render().el);
this._listView.highlight();
this.addView(this._listView);
this._listView.bind('customEvent', function (eventName, item) {
this.trigger(eventName, item, this);
}, this);
},
highlight: function () {
this._listView.highlight();
},
_onVisibleChanged: function (_model, isVisible) {
this._resetQuery();
this._toggleVisibility();
isVisible ? this.render() : this.clearSubViews();
},
_resetQuery: function () {
var query = this._selectModel && this._selectModel.get(this.options.typeLabel) || '';
this.model.set('query', query);
var isInCollection = this.collection.findWhere({ val: query });
if (query && !isInCollection) {
this.collection.add({ label: query, val: query });
}
},
show: function () {
this.model.set('visible', true);
},
hide: function () {
this.trigger('hidden', this);
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
_toggleVisibility: function () {
this.$el.toggleClass('is-visible', !!this.model.get('visible'));
},
isVisible: function () {
return this.model.get('visible');
},
_onMouseOver: function () {
this._mouseOverAction && this._mouseOverAction();
},
_onMouseOut: function () {
this._mouseOutAction && this._mouseOutAction();
},
remove: function () {
this._listView && this._listView.clean();
CoreView.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,167 @@
var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var SQLNotifications = require('builder/sql-notifications');
var SQLUtils = require('builder/helpers/sql-utils');
var Notifier = require('builder/components/notifier/notifier');
var cdb = require('internal-carto.js');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'configModel',
'editorModel',
'layerDefinitionModel',
'querySchemaModel'
];
module.exports = CoreView.extend({
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._clearSQLModel = new Backbone.Model({
visible: false
});
this._sqlModel = this._layerDefinitionModel.sqlModel;
this._SQL = new cdb.SQL({
user: this._configModel.get('user_name'),
sql_api_template: this._configModel.get('sql_api_template'),
api_key: this._configModel.get('api_key')
});
this._codemirrorModel = new Backbone.Model({
content: this._querySchemaModel.get('query'),
readonly: false
});
this._applyButtonStatusModel = new Backbone.Model({
loading: false
});
SQLNotifications.track(this);
},
_internalParseSQL: function (callbackAfterAlterSuccess) {
var appliedQuery = this._querySchemaModel.get('query');
var currentQuery = this._codemirrorModel.get('content');
// Remove last character if it has ';'
if (currentQuery && currentQuery.slice(-1) === ';') {
currentQuery = currentQuery.slice(0, currentQuery.length - 1);
this._codemirrorModel.set('content', currentQuery);
}
var isSameQuery = SQLUtils.isSameQuery(currentQuery, appliedQuery);
var altersData = SQLUtils.altersData(currentQuery);
if (currentQuery === '' || isSameQuery) {
SQLNotifications.removeNotification();
return false;
}
if (altersData) {
SQLNotifications.showNotification({
status: 'loading',
info: _t('notifications.sql.alter-loading'),
closable: false
});
this._applyButtonStatusModel.set('loading', true);
this._sqlModel.set('content', currentQuery);
this._SQL.execute(currentQuery, null, {
success: function () {
SQLNotifications.showNotification({
status: 'success',
info: _t('notifications.sql.alter-success'),
closable: true,
delay: Notifier.DEFAULT_DELAY
});
this._applyButtonStatusModel.set('loading', false);
this._applyDefaultSQLAfterAlteringData();
if (typeof callbackAfterAlterSuccess === 'function') {
callbackAfterAlterSuccess.call(this);
}
}.bind(this),
error: function (errors) {
var parsedErrors = this._parseErrors(errors.responseJSON && errors.responseJSON.error);
this._applyButtonStatusModel.set('loading', false);
this._codemirrorModel.set('errors', parsedErrors);
SQLNotifications.showErrorNotification(parsedErrors);
this._checkClearButton();
}.bind(this)
});
} else {
this._runQuery(currentQuery, this._saveSQL.bind(this));
}
},
_checkClearButton: function () {
var customSql = this._codemirrorModel.get('content');
var isDefaultQuery = SQLUtils.isSameQuery(customSql, this._defaultSQL());
this._clearSQLModel.set({ visible: !isDefaultQuery });
},
_applyDefaultSQLAfterAlteringData: function () {
var originalQuery = this._defaultSQL();
this._codemirrorModel.set({
content: originalQuery,
errors: []
});
this._sqlModel.set('content', originalQuery);
this._querySchemaModel.set('query_errors', []);
this._clearSQLModel.set({ visible: false });
this._querySchemaModel.resetDueToAlteredData();
this._clearSQL();
},
_clearSQL: function () {
var originalQuery = this._defaultSQL();
this._codemirrorModel.set({
content: originalQuery,
errors: []
});
this._runQuery(originalQuery, this._saveSQL.bind(this));
},
_showErrors: function (model) {
var errors = this._querySchemaModel.get('query_errors');
this._forceErrors(errors);
this._checkClearButton();
},
_forceErrors: function (errors, options) {
var hasErrors = errors && errors.length > 0;
var parsedErrors = hasErrors && this._parseErrors(errors);
var editorErrors = options && options.showEditorError === false ? [] : parsedErrors;
this._codemirrorModel.set('errors', editorErrors);
this._editorModel.set('disabled', hasErrors);
if (hasErrors) {
SQLNotifications.showErrorNotification(parsedErrors);
}
},
_parseErrors: function (errors) {
if (!errors) {
return [];
}
errors = _.isArray(errors) ? errors : [errors];
return errors.map(function (error) {
return {
message: error
};
});
}
});

View File

@@ -0,0 +1,72 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var $ = require('jquery');
var moment = require('moment');
var template = require('./calendar-dropdown.tpl');
/**
* Dropdown for a calendar selector.
* Uses the DatePicker plugin internally to render the calendar and view behaviour.
*/
module.exports = CoreView.extend({
className: 'Dropdown',
// defaults, used for
options: {
flat: true,
date: '2008-07-01',
current: '2008-07-31',
calendars: 1,
starts: 1
},
initialize: function () {
if (!this.model) throw new Error('model is required');
this.elder('initialize');
this._initDefaults();
},
render: function () {
this.$el.html(
template({
initialDateStr: this.model.get('date')
})
);
$('body').append(this.el);
this._initCalendar(); // must be called after element is added to body!
return this;
},
_initDefaults: function () {
var utc = new Date().getTimezoneOffset();
var today = moment(new Date()).utcOffset(utc).format('YYYY-MM-DD');
this.options.current = today;
this.options.date = today;
},
// should not be called until element is located in document
_initCalendar: function () {
var self = this;
this._$calendar().DatePicker(
_.extend(this.options, this.model.attributes, {
onChange: function (formatted) {
self.model.set('date', formatted);
self.$('.js-date-str').text(formatted);
self.hide();
}
})
);
},
_$calendar: function () {
return this.$('.js-calendar');
},
clean: function () {
this._$calendar().DatePickerHide();
CoreView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,3 @@
<div class="DatePicker">
<div class="js-calendar DatePicker-calendar DatePicker-calendar--simple"></div>
</div>

View File

@@ -0,0 +1,22 @@
<form>
<div class="DatePicker-timersFrom">
<div class="DatePicker-timersHour">
<label class="DatePicker-timersLabel u-upperCase"><%- _t('components.datepicker.hour') %></label>
<div data-editors="fromHour"></div>
</div>
<div class="DatePicker-timersMin">
<label class="DatePicker-timersLabel u-upperCase"><%- _t('components.datepicker.min') %></label>
<div data-editors="fromMin"></div>
</div>
</div>
<div class="DatePicker-timersTo">
<div class="DatePicker-timersHour">
<label class="DatePicker-timersLabel u-upperCase"><%- _t('components.datepicker.hour') %></label>
<div data-editors="toHour"></div>
</div>
<div class="DatePicker-timersMin">
<label class="DatePicker-timersLabel u-upperCase"><%- _t('components.datepicker.min') %></label>
<div data-editors="toMin"></div>
</div>
</div>
</form>

View File

@@ -0,0 +1,14 @@
<form class="DatePicker-timers">
<div class="DatePicker-timersField u-rSpace--xl">
<div class="DatePicker-timersInput" data-fields="fromHour"></div>
</div>
<div class="DatePicker-timersField u-rSpace--xl">
<div class="DatePicker-timersInput" data-fields="fromMin"></div>
</div>
<div class="DatePicker-timersField DatePicker-timersField--spaced u-rSpace--xl">
<div class="DatePicker-timersInput" data-fields="toHour"></div>
</div>
<div class="DatePicker-timersField">
<div class="DatePicker-timersInput" data-fields="toMin"></div>
</div>
</form>

View File

@@ -0,0 +1,262 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var moment = require('moment');
var $ = require('jquery');
var Utils = require('builder/helpers/utils');
require('builder/components/form-components/index');
require('datepicker');
var datePickerTemplate = require('./date-picker-range-form.tpl');
var template = require('./date-picker-range.tpl');
var MAX_RANGE = 30;
var FOUR_HOURS = { amount: 4, unit: 'hours' };
var ONE_DAY = { amount: 1, unit: 'day' };
var ONE_WEEK = { amount: 1, unit: 'week' };
/**
* Custom picer for a dates range.
*/
module.exports = CoreView.extend({
className: 'DatePicker',
options: {
flat: true,
date: ['2008-07-31', '2008-07-31'],
current: '2008-07-31',
calendars: 2,
mode: 'range',
starts: 1
},
events: {
'click .js-dates': '_toggleCalendar',
'click .js-fourHours': function () { this._setPreviousTime(FOUR_HOURS); },
'click .js-oneDay': function () { this._setPreviousTime(ONE_DAY); },
'click .js-oneWeek': function () { this._setPreviousTime(ONE_WEEK); }
},
initialize: function (opts) {
var isDisabled = (opts && opts.disabled) ? opts.disabled : false;
this.model = new Backbone.Model({
fromDate: '',
fromHour: 0,
fromMin: 0,
toDate: '',
toHour: 23,
toMin: 59,
user_timezone: 0, // Explained as GMT+0
disabled: isDisabled
});
this.template = opts.template || template;
this._initBinds();
this._setDefaultDate();
},
render: function () {
var self = this;
this.clearSubViews();
this.$el.empty();
this.$el.append(
this.template(
_.extend(
this.model.attributes,
{
max_days: MAX_RANGE,
pad: Utils.pad
}
)
)
);
setTimeout(function () {
self._initCalendar();
self._hideCalendar();
self._initTimers();
}, 100);
return this;
},
_initBinds: function () {
this.model.bind('change', this._setValues, this);
this.model.bind('change', this._onValuesChange, this);
$(document).bind('click', this._onDocumentClick.bind(this));
},
_destroyBinds: function () {
$(document).unbind('click', this._onDocumentClick.bind(this));
},
_formattedDate: function (which) {
var text = 'components.datepicker.' + which;
return _t(text) + ' ' +
'<strong>' +
this.model.get(which + 'Date') + ' ' +
(Utils.pad(this.model.get(which + 'Hour'), 2) + ':' + Utils.pad(this.model.get(which + 'Min'), 2)) +
'</strong>';
},
_setValues: function () {
var text = _t('components.datepicker.dates-placeholder');
var data = this.model.attributes;
if (data.fromDate && data.toDate) {
var calendarIcon = '<i class="CDB-IconFont CDB-IconFont-calendar DatePicker-datesIcon"></i>';
text = this._formattedDate('from') + ' ' + this._formattedDate('to') + calendarIcon;
}
this.$('.DatePicker-dates').html(text);
},
_setDefaultDate: function () {
var datesUTC = this.model.get('user_timezone');
var today = moment().utc(datesUTC);
var previous = moment().utc(datesUTC).subtract((MAX_RANGE - 1), 'days');
this.options.date = [previous.format('YYYY-MM-DD'), today.format('YYYY-MM-DD')];
this.options.current = today.format('YYYY-MM-DD');
this._setModelFromPrevious(previous);
},
_initCalendar: function () {
var selector = '.DatePicker-calendar';
// Can't initialize calendar if not already present in document... avoid errors being thrown
if (!document.body.contains(this.$(selector)[0])) return;
this.calendar = this.$(selector).DatePicker(
_.extend(this.options, {
onChange: this._onDatesChange.bind(this),
onRender: function (d) { // Disable future dates and dates < MAX_RANGE days ago
var date = d.valueOf();
var now = new Date();
var thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(now.getDate() - MAX_RANGE);
return (date < thirtyDaysAgo) || (date > now) ? { disabled: true } : '';
}
})
);
},
_onDatesChange: function (formatted, dates) {
// Check if selected dates have more than MAX_RANGE days
var start = moment(formatted[0]);
var end = moment(formatted[1]);
if (Math.abs(start.diff(end, 'days')) > MAX_RANGE) {
formatted[1] = moment(formatted[0]).add('days', MAX_RANGE).format('YYYY-MM-DD');
this.$('.DatePicker-calendar').DatePickerSetDate([formatted[0], formatted[1]]);
}
this.model.set({
fromDate: formatted[0],
toDate: formatted[1]
});
},
_hideCalendar: function (e) {
if (e) this.killEvent(e);
this.$('.DatePicker-dropdown').hide();
},
_toggleCalendar: function (ev) {
if (ev) this.killEvent(ev);
this.$('.DatePicker-dropdown').toggle();
},
_setPreviousTime: function (timeSpan) {
var previous = moment().utc(0).subtract(timeSpan.amount, timeSpan.unit);
this._setModelFromPrevious(previous);
this._setDatepickerFromPrevious(previous);
this.closeCalendar();
},
_setModelFromPrevious: function (previous) {
var today = moment().utc(0);
this.model.set({
fromDate: previous.format('YYYY-MM-DD'),
fromHour: parseInt(previous.format('H'), 10),
fromMin: parseInt(previous.format('m'), 10),
toDate: today.format('YYYY-MM-DD'),
toHour: parseInt(today.format('H'), 10),
toMin: parseInt(today.format('m'), 10)
});
},
_setDatepickerFromPrevious: function (previous) {
var today = moment().utc(0);
this.$('.DatePicker-calendar').DatePickerSetDate([ previous.format('YYYY-MM-DD'), today.format('YYYY-MM-DD') ]);
},
_initTimers: function () {
var generateNumberType = function (min, max) {
var title = max === 23
? _t('components.datepicker.hour')
: _t('components.datepicker.min');
return {
type: 'Number',
title: title,
validators: ['required', {
type: 'interval',
min: min,
max: max
}]
};
};
this.model.schema = {
fromHour: generateNumberType(0, 23),
fromMin: generateNumberType(0, 59),
toHour: generateNumberType(0, 23),
toMin: generateNumberType(0, 59)
};
this._datesForm = new Backbone.Form({
model: this.model,
template: datePickerTemplate
});
this._datesForm.bind('change', function () {
this.commit();
});
this.$('.js-timers').append(this._datesForm.render().el);
},
_onValuesChange: function () {
this.trigger('changeDate', this.model.toJSON(), this);
},
getDates: function () {
return this.model.toJSON();
},
closeCalendar: function () {
this.$('.DatePicker-dropdown').hide();
},
_onDocumentClick: function (e) {
var $el = $(e.target);
if ($el.closest('.DatePicker').length === 0) {
this.closeCalendar();
}
},
clean: function () {
this._datesForm && this._datesForm.remove();
this._destroyBinds();
this.closeCalendar();
this.$('.DatePicker-calendar').DatePickerHide();
CoreView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,16 @@
<button <% if (disabled) { %>disabled="disabled"<% } %> class="DatePicker-dates js-dates has-icon CDB-Text CDB-Size-medium <%- disabled ? 'is-disabled' : '' %>">
<%- _t('components.datepicker.from') %> <strong><%- fromDate %> <%- pad(fromHour,2) %>:<%- pad(fromMin,2) %></strong> <%- _t('components.datepicker.to') %> <strong><%- toDate %> <%- pad(toHour,2) %>:<%- pad(toMin,2) %></strong>
<i class="CDB-IconFont CDB-IconFont-calendar DatePicker-datesIcon"></i>
</button>
<div class="DatePicker-dropdown CDB-Text">
<div class="DatePicker-calendar"></div>
<div class="DatePicker-timers js-timers"></div>
<div class="DatePicker-shortcuts">
<p class="DatePicker-shortcutsText">
<%- _t('components.datepicker.get-last') %> <button type="button" class="Button--link js-fourHours"><%- _t('components.datepicker.hours-pluralize', { smart_count: 4 }) %></button>,
<button type="button" class="Button--link js-oneDay"><%- _t('components.datepicker.days-pluralize', { smart_count: 1 }) %></button> <%- _t('components.datepicker.or') %>
<button type="button" class="Button--link js-oneWeek"><%- _t('components.datepicker.weeks-pluralize', { smart_count: 1 }) %></button>
</p>
<p class="DatePicker-shortcutsText"><%- _t('components.datepicker.gmt-convertion') %></p>
</div>
</div>

View File

@@ -0,0 +1,135 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var moment = require('moment');
var $ = require('jquery');
require('builder/components/form-components/index');
require('datepicker');
var template = require('./date-picker.tpl');
/**
* Custom picer for a dates range.
*/
module.exports = CoreView.extend({
className: 'DatePicker',
options: {
flat: true,
date: '2008-07-31',
current: '2008-07-31',
calendars: 1,
starts: 1
},
events: {
'click .js-dates': '_toggleCalendar'
},
initialize: function (opts) {
this.template = opts.template || template;
this._initBinds();
this._setDefaultDate();
},
render: function () {
this.clearSubViews();
this.$el.append(this.template({
readOnly: this.model.get('readOnly'),
date: this.options.date
}));
if (this.model.get('readOnly')) {
this.undelegateEvents();
}
return this;
},
_initBinds: function () {
$(document).bind('click', this._onDocumentClick.bind(this));
},
_destroyBinds: function () {
$(document).unbind('click', this._onDocumentClick.bind(this));
},
_setDefaultDate: function () {
var utc = new Date().getTimezoneOffset();
var today = moment(new Date()).utcOffset(utc).format('YYYY-MM-DD');
this.options.date = this.model.get('value') && moment(this.model.get('value')).format('YYYY-MM-DD') || today;
this.options.current = this.options.date;
},
_$calendar: function () {
return this.$('.js-calendar');
},
_$dropdown: function () {
return this.$('.js-DatePicker-simpleDropdown');
},
_hideCalendar: function (ev) {
if (ev) {
this.killEvent(ev);
}
this.model.set('visible', false);
this._destroyCalendar();
this._$dropdown().hide();
},
_showCalendar: function () {
this.model.set('visible', true);
this._$calendar().DatePicker(
_.extend(this.options, this.model.attributes, {
onChange: function (formatted, date) {
this.model.set('value', formatted);
this.$('.js-date-str').text(formatted);
this._hideCalendar();
}.bind(this),
onRender: function (d) { // Disable future dates and dates
var date = d.valueOf();
var now = new Date();
return (date > now) ? { disabled: true } : '';
}
})
);
this._$dropdown().show();
},
_destroyCalendar: function () {
if (this.$('.DatePicker').length > 0) {
this._$calendar() && this._$calendar().DatePickerHide();
}
},
_toggleCalendar: function (ev) {
if (ev) {
this.killEvent(ev);
}
this.model.get('visible') ? this._hideCalendar() : this._showCalendar();
},
closeCalendar: function () {
this._hideCalendar();
},
_onDocumentClick: function (e) {
var $el = $(e.target);
if ($el.closest('.DatePicker').length === 0) {
this._hideCalendar();
}
},
clean: function () {
this._destroyBinds();
this._hideCalendar();
CoreView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,7 @@
<button class="js-dates DatePicker-dates DatePicker-dates--singleDate js-DatePicker-dates has-icon <% if (readOnly) { %> is-disabled <% } %>">
<strong class="js-date-str"><%- date %></strong>
<i class="CDB-IconFont CDB-IconFont-calendar DatePicker-datesIcon"></i>
</button>
<div class="DatePicker-simpleDropdown datepickerHidden js-DatePicker-simpleDropdown">
<div class="js-calendar DatePicker-calendar DatePicker-calendar--simple"></div>
</div>

View File

@@ -0,0 +1,76 @@
var Backbone = require('backbone');
var moment = require('moment');
/**
* Default model for each field model
*
*/
module.exports = Backbone.Model.extend({
defaults: {
attribute: '',
value: '',
type: 'string',
readOnly: false
},
initialize: function () {
// Validation control variable
this.validationError = '';
this.bind('valid', function () {
this.validationError = '';
}, this);
this.bind('error', function (m, error) {
this.validationError = error;
});
},
_validate: function (attrs, options) {
var valid = Backbone.Model.prototype._validate.apply(this, arguments);
if (valid) {
this.trigger('valid');
return true;
} else {
return false;
}
},
validate: function (attrs) {
if (!attrs) return;
var val = attrs.value;
var type = attrs.type;
if (attrs.type === 'number') {
var pattern = /^(\+|-)?(?:[0-9]+|[0-9]*\.[0-9]+)$/;
if (val && !pattern.test(val)) {
return 'Invalid number';
}
}
if (type === 'boolean') {
if (val !== null && val !== true && val !== false) {
return 'Invalid boolean';
}
}
if (type === 'date') {
if (val && !moment(val).isValid()) {
return 'Invalid date';
}
}
},
getError: function () {
return this.validationError;
},
isValid: function () {
if (!this.validate) {
return true;
}
return !this.validate(this.attributes) && this.validationError === '';
}
});

View File

@@ -0,0 +1,38 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
/**
* View model of the dialog-view
*/
module.exports = Backbone.Model.extend({
defaults: {
visible: true,
createContentView: function () {
return new CoreView();
}
},
createContentView: function () {
return this.get('createContentView')(this);
},
show: function () {
this.set('visible', true);
},
hide: function () {
this.set('visible', false);
},
isHidden: function () {
return !this.get('visible');
},
/**
* @override {Backbone.Model.prototype.destroy}
*/
destroy: function () {
var args = Array.prototype.slice.call(arguments);
this.trigger.apply(this, ['destroy'].concat(args));
}
});

View File

@@ -0,0 +1,57 @@
var CoreView = require('backbone/core-view');
var DropdownOverlayView = require('builder/components/dropdown-overlay/dropdown-overlay-view');
module.exports = CoreView.extend({
className: 'Editor-boxModal Editor-FormDialog js-formDialog is-opening',
initialize: function () {
this._initBinds();
},
render: function () {
this.clearSubViews();
this._renderContentView();
return this;
},
_renderContentView: function () {
var view = this.model.createContentView();
this.addView(view);
this.$el.append(view.render().$el);
this.dropdownOverlay = new DropdownOverlayView({
onClickAction: this.hide.bind(this),
visible: true
});
this.addView(this.dropdownOverlay);
},
_initBinds: function () {
this.listenTo(this.model, 'change:show', this._onShowChange);
this.listenTo(this.model, 'destroy', this._onDestroy);
},
_onShowChange: function (m, show) {
if (show) {
this.$el.show();
this.$el.removeClass('is-closing').addClass('is-opening');
} else {
this.$el.removeClass('is-opening').addClass('is-closing');
this.$el.hide();
}
},
show: function () {
this.model.show();
},
hide: function () {
this.model.hide();
},
_onDestroy: function () {
this.hide();
this.dropdownOverlay && this.dropdownOverlay.clean();
this.clean();
}
});

View File

@@ -0,0 +1,64 @@
var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
/*
* Dropdown overlay to disable all interactions between elements
* beneath the dropdown
*/
module.exports = CoreView.extend({
className: 'CDB-Box-modalOverlay',
events: {
'click': '_onOverlayClicked'
},
initialize: function (opts) {
this.container = opts && opts.container;
this.onClickAction = opts && opts.onClickAction;
this.model = new Backbone.Model({
visible: _.isUndefined(opts && opts.visible) ? false : opts.visible
});
this._initBinds();
this.render();
},
_initBinds: function () {
this.listenTo(this.model, 'change:visible', this._onVisibilityChange);
},
render: function () {
(this.container || $(document.body)).append(this.$el);
this.$el.toggle(this.isVisible());
return this;
},
_onOverlayClicked: function () {
this.onClickAction && this.onClickAction();
this.hide();
},
show: function () {
this.model.set('visible', true);
},
hide: function () {
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
isVisible: function () {
return this.model.get('visible');
},
_onVisibilityChange: function () {
this.$el.toggle(this.isVisible());
}
});

View File

@@ -0,0 +1,41 @@
var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var cdb = require('internal-carto.js');
var baseTemplate = require('./error.tpl');
/**
* A typical error view.
* @param {Object} options
* @param {String} options.title If not provided will use a generic text as fallback
* @param {String} options.desc If not provied will use a generic text as fallback
*/
module.exports = CoreView.extend({
className: 'IntermediateInfo',
initialize: function (opts) {
var attrs = _.defaults(
_.pick(opts, ['title', 'desc']),
{
title: _t('components.error.default-title'),
desc: _t('components.error.default-desc')
}
);
this._template = this.options && this.options.template || baseTemplate;
this.model = new Backbone.Model(attrs);
},
render: function () {
this.$el.html(this._html());
return this;
},
_html: function () {
var m = this.model;
return this._template({
title: m.get('title'),
desc: cdb.core.sanitize.html(m.get('desc'))
});
}
});

View File

@@ -0,0 +1,5 @@
<div class="LayoutIcon LayoutIcon--negative">
<i class="CDB-IconFont CDB-IconFont-cockroach"></i>
</div>
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-errorTextColor u-bSpace--m u-tSpace-xl"><%- title %></h3>
<p class="CDB-Text CDB-Size-medium u-altTextColor"><%= desc %></p>

View File

@@ -0,0 +1,7 @@
module.exports = {
Mode: {
NESTED: 'nested',
FLOAT: 'float',
DEFAULT: 'nested'
}
};

View File

@@ -0,0 +1,68 @@
module.exports = {
Size: {
MARKER_MIN: 7,
IMAGE_MIN: 20,
DEFAULT: {
min: 1,
max: 45,
step: 0.5
},
DefaultInput100: {
MIN: 0,
MAX: 100,
STEP: 1
},
DEFAULT_RANGE: [ 5, 20 ]
},
Panes: {
FIXED: 'fixed',
BY_VALUE: 'value',
FILE: 'file'
},
Tabs: {
BINS: 'bins',
QUANTIFICATION: 'quantification'
},
Quantification: {
REFERENCE: {
'Jenks': 'jenks',
'Equal Interval': 'equal',
'Heads/Tails': 'headtails',
'Quantile': 'quantiles'
}
},
Settings: {
COLOR: {
quantifications: {
items: ['jenks', 'equal', 'headtails', 'quantiles', 'category'],
defaultIndex: 0
}
},
COLOR_RAMPS: {
quantifications: {
items: ['quantiles', 'jenks', 'equal', 'headtails', 'category'],
defaultIndex: 0
},
bins: {
items: ['2', '3', '4', '5', '6', '7'],
defaultIndex: 3
}
},
NUMBER: {
quantifications: {
items: ['quantiles', 'jenks', 'equal', 'headtails'],
defaultIndex: 0
},
bins: {
items: ['2', '3', '4', '5', '6', '7'],
defaultIndex: 3
}
}
}
};

View File

@@ -0,0 +1,32 @@
module.exports = {
Type: {
SIMPLE: 'simple',
ANIMATION: 'animation',
HEATMAP: 'heatmap',
POLYGON: 'polygon',
REGIONS: 'regions',
HEXABINS: 'hexabins',
SQUARES: 'squares',
NONE: 'none'
},
Blending: {
SIMPLE: [
'none',
'multiply',
'screen',
'overlay',
'darken',
'lighten',
'color-dodge',
'color-burn',
'xor',
'src-over'
],
ANIMATION: [
'lighter',
'multiply',
'source-over',
'xor'
]
}
};

View File

@@ -0,0 +1,62 @@
var Backbone = require('backbone');
var $ = require('jquery');
var ESC_KEY_CODE = 27;
Backbone.Form.editors.Base = Backbone.Form.editors.Base.extend({
applyESCBind: function (callback) {
this._ESCBindCallback = callback;
this._onKeyDown = this._onKeyDown.bind(this);
document.addEventListener('keydown', this._onKeyDown);
},
_onKeyDown: function (ev) {
var anyModalOpen;
if (ev.which === ESC_KEY_CODE) {
anyModalOpen = this._anyModalOpen();
!anyModalOpen && this._ESCBindCallback();
}
},
applyClickOutsideBind: function (callback) {
this._clickBindCallback = callback;
this._onDocumentClick = this._onDocumentClick.bind(this);
this.$el.attr('data-cid', this.cid);
document.addEventListener('click', this._onDocumentClick);
},
_onDocumentClick: function (e) {
var $el = $(e.target);
var anyModalOpen = this._anyModalOpen();
if ($el.closest('[data-cid="' + this.cid + '"]').length === 0 && !anyModalOpen) {
this._clickBindCallback();
}
},
_anyModalOpen: function () {
var modals = this.options.modals;
if (!modals) {
return false;
}
return modals.isOpen();
},
_destroyBinds: function () {
this._removeESCandClickListeners();
},
_removeESCandClickListeners: function () {
if (this._ESCBindCallback) {
document.removeEventListener('keydown', this._onKeyDown);
}
if (this._clickBindCallback) {
document.removeEventListener('click', this._onDocumentClick);
}
},
remove: function () {
this._destroyBinds && this._destroyBinds();
Backbone.View.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,88 @@
var $ = require('jquery');
var Backbone = require('backbone');
var FactoryHints = require('builder/editor/editor-hints/factory-hints');
var CodeMirrorView = require('builder/components/code-mirror/code-mirror-view');
Backbone.Form.editors.CodeEditor = Backbone.Form.editors.TextArea.extend({
render: function () {
this.setValue(this.value);
this._codemirrorModel = new Backbone.Model({
content: this.value,
readonly: false,
lineNumbers: false
});
FactoryHints.init({
tokens: this.options.tokens,
tableName: false,
columnsName: false
});
this._initViews();
this._toggleDisableState();
return this;
},
getValue: function () {
var val = this.$el.val();
val = this.codeMirrorView.getContent();
return (val === '') ? null : val;
},
_initViews: function () {
this._destroyEditor();
var hints = FactoryHints.reset().hints;
this.codeMirrorView = new CodeMirrorView({
model: this._codemirrorModel,
hints: hints,
mode: 'text/mustache',
autocompleteChars: 2,
autocompletePrefix: '{{',
autocompleteSuffix: '}}',
placeholder: this.options.placeholder
});
this.codeMirrorView.bind('codeChanged', function () {
this.trigger('change', this.codeMirrorView.getContent());
}, this);
var $codeMirrorEl = $(this.codeMirrorView.render().el);
this.$el.replaceWith($codeMirrorEl);
this.setElement($codeMirrorEl);
this.$el.addClass('CodeMirror-formInput');
// The default el is replace it with another dom node
// we should add tracking class manually again
this._addTrackingClass();
},
_addTrackingClass: function () {
if (this.options.trackingClass) {
var trackClasses = this.options.trackingClass + ' track-' + this.options.key + this.options.editorType;
this.$el.addClass(trackClasses);
}
},
_hasEditor: function () {
return this.options.editor && !!this.codeMirrorView;
},
_destroyEditor: function () {
if (this._hasEditor()) {
this.codeMirrorView.remove();
}
},
remove: function () {
this._destroyEditor();
Backbone.Form.editors.Base.prototype.remove.apply(this);
},
clean: function () {
this.$el.remove();
}
});

View File

@@ -0,0 +1,183 @@
var Backbone = require('backbone');
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var StackLayoutView = require('builder/components/stack-layout/stack-layout-view');
var MeasurementsView = require('./measurements-list-view');
var FiltersView = require('./filters-list-view');
var REQUIRED_OPTS = [
'filtersCollection',
'measurementsCollection',
'measurementModel',
'region'
];
var braces = function (value) {
var template = _.template("'{<%- value %>}'");
return template({
value: value
});
};
module.exports = CoreView.extend({
className: 'CDB-Box-modal CustomList',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
// For internal state
this.model = new Backbone.Model({
visible: false
});
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this._generateStackLayoutView();
return this;
},
_initBinds: function () {
this.listenTo(this.model, 'change:visible', function (mdl, isVisible) {
isVisible ? this.render() : this.clearSubViews();
this._toggleVisibility();
});
},
_generateStackLayoutView: function () {
var createListView = function (stackLayoutModel, opts) {
return this._createListView(stackLayoutModel, opts);
}.bind(this);
var createFilterView = function (stackLayoutModel, opts) {
return this._createFilterView(stackLayoutModel, opts);
}.bind(this);
var stackViewCollection = new Backbone.Collection([{
createStackView: createListView
}, {
createStackView: createFilterView
}]);
this._stackLayoutView = new StackLayoutView({ collection: stackViewCollection });
this.addView(this._stackLayoutView);
this.$el.append(this._stackLayoutView.render().$el);
},
_buildFilters: function () {
var selectedFilters = this._filtersCollection.getSelected();
return _.map(selectedFilters, function (filter) {
return filter.getValue();
}).join(', ');
},
_fetchMeasurements: function () {
var region = this._region;
var fetchOptions = {
region: region && braces(region)
};
return this._measurementsCollection.fetch(fetchOptions);
},
_fetchMeasurementsWithFilter: function () {
var region = this._region;
var filters = this._buildFilters();
var fetchOptions = {
filters: filters && braces(filters),
region: region && braces(region)
};
return this._measurementsCollection.fetch(fetchOptions);
},
_searchMeasurements: function (keyword) {
var region = this._region;
var filters = this._buildFilters();
var fetchOptions = {
filters: filters && braces(filters),
region: region && braces(region),
search: keyword,
abortable: true
};
return this._measurementsCollection.fetch(fetchOptions);
},
_fetchFilters: function () {
var region = this._region;
var fetchOptions = {
region: region && braces(region)
};
return this._filtersCollection.fetch(fetchOptions);
},
_fetchCollections: function () {
var selectedFilters = this._filtersCollection.getSelected();
if (selectedFilters.length > 0) {
this._fetchMeasurementsWithFilter();
} else {
this._fetchMeasurements();
}
},
_createListView: function (stackLayoutModel, opts) {
this._fetchCollections();
var view = new MeasurementsView({
filtersCollection: this._filtersCollection,
measurementsCollection: this._measurementsCollection,
searchMeasurements: this._searchMeasurements.bind(this),
measurementModel: this._measurementModel
});
view.bind('filters', function () {
stackLayoutModel.nextStep();
}, this);
return view;
},
_createFilterView: function (stackLayoutModel, opts) {
// FIXME
// we could save this request, it only depends on region
if (this._filtersCollection.size() === 0) {
this._fetchFilters();
}
var view = new FiltersView({
filtersCollection: this._filtersCollection
});
view.bind('back', function () {
this._measurementsCollection.trigger('maybeFiltersUpdated');
stackLayoutModel.prevStep();
}, this);
return view;
},
hide: function () {
this.model.set('visible', false);
},
toggle: function () {
this.model.set('visible', !this.model.get('visible'));
},
isVisible: function () {
return this.model.get('visible');
},
_toggleVisibility: function () {
this.$el.toggleClass('is-visible', !!this.isVisible());
}
});

View File

@@ -0,0 +1,12 @@
<% if (typeof isLoading != 'undefined' && isLoading) { %>
<div class="u-flex">
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
</div>
<% } else { %>
<%- label %>
<% } %>

View File

@@ -0,0 +1,241 @@
var Backbone = require('backbone');
var _ = require('underscore');
var EditorHelpers = require('builder/components/form-components/helpers/editor');
var template = require('./data-observatory-measurements.tpl');
var selectedItemTemplate = require('./data-observatory-measurement-item.tpl');
var DropdownDialogView = require('./data-observatory-dropdown-measurements-view');
var PopupManager = require('builder/components/popup-manager');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var MeasurementsCollection = require('builder/data/data-observatory/measurements-collection');
var FiltersCollection = require('builder/data/data-observatory/filters-collection');
var ENTER_KEY_CODE = 13;
var STATE = {
idle: 'idle',
loading: 'loading',
fetching: 'fetching',
fetched: 'fetched',
error: 'error'
};
var MEASUREMENT_ATTRIBUTES = ['aggregate', 'type', 'label', 'val', 'description', 'filter', 'license'];
Backbone.Form.editors.DataObservatoryDropdown = Backbone.Form.editors.Base.extend({
tagName: 'div',
className: 'u-ellipsis Editor-formSelect',
events: {
'click .js-button': '_onButtonClick',
'keydown .js-button': '_onButtonKeyDown',
'focus .js-button': function () {
this.trigger('focus', this);
},
'blur': function () {
this.trigger('blur', this);
}
},
options: {
selectedItemTemplate: selectedItemTemplate
},
initialize: function (opts) {
Backbone.Form.editors.Base.prototype.initialize.call(this, opts);
EditorHelpers.setOptions(this, opts);
this.template = opts.template || template;
this.dialogMode = this.options.dialogMode || DialogConstants.Mode.DEFAULT;
this.measurementModel = this.options.measurementModel;
var fetchOptions = {
configModel: this.options.configModel,
nodeDefModel: this.options.nodeDefModel
};
this.measurementsCollection = new MeasurementsCollection([], fetchOptions);
this.filtersCollection = new FiltersCollection([], fetchOptions);
this._initBinds();
this._dialogView = new DropdownDialogView({
configModel: this.options.configModel,
nodeDefModel: this.options.nodeDefModel,
measurementsCollection: this.measurementsCollection,
filtersCollection: this.filtersCollection,
measurementModel: this.measurementModel,
region: this.options.region
});
},
render: function () {
var isLoading = this._isLoading();
var isDisabled = this.options.disabled;
var item = this.measurementModel;
var placeholder = this._getPlaceholder();
var isNull = this._hasValue();
var label = isNull ? placeholder : item.getName();
var title = item.getName() || '';
this.$el.html(
this.template({
title: title,
label: label,
keyAttr: this.options.keyAttr,
isDisabled: isDisabled,
isLoading: isLoading,
isNull: isNull
})
);
this._popupManager = new PopupManager(this.cid, this.$el, this._dialogView.$el);
this._popupManager.append(this.dialogMode);
if (item) {
this._renderLicense(item);
}
return this;
},
_initBinds: function () {
var hide = function () {
this._dialogView.hide();
this._popupManager && this._popupManager.untrack();
}.bind(this);
this.applyESCBind(hide);
this.applyClickOutsideBind(hide);
this.listenTo(this.measurementsCollection, 'change:selected', this._onItemSelected);
},
_destroyBinds: function () {
this.stopListening(this.measurementsCollection);
Backbone.Form.editors.Base.prototype._destroyBinds.call(this);
},
_getPlaceholder: function (isDisabled) {
var keyAttr = this.options.keyAttr;
var placeholder = this.options.placeholder || _t('components.backbone-forms.select.placeholder', { keyAttr: keyAttr });
return placeholder;
},
_hasValue: function () {
var name = this.measurementModel.getValue();
return name == null || name === '';
},
_isLoading: function () {
var state = this.measurementModel.getState();
return state === STATE.fetching;
},
_onItemSelected: function (mdl) {
var selected = this.measurementsCollection.getSelectedItem();
if (selected) {
this.measurementModel.clear({silent: true});
this.measurementModel.set(_.pick(selected.attributes, MEASUREMENT_ATTRIBUTES));
}
this._dialogView.hide();
this._popupManager.untrack();
this._renderLicense(selected);
this._renderButton(selected).focus();
this.trigger('change', this);
},
_onButtonClick: function () {
this._dialogView.toggle();
this._dialogView.isVisible() ? this._popupManager.track() : this._popupManager.untrack();
},
_onButtonKeyDown: function (ev) {
if (ev.which === ENTER_KEY_CODE) {
ev.preventDefault();
if (!this._dialogView.isVisible()) {
ev.stopPropagation();
this._onButtonClick();
} else {
this._popupManager.track();
}
}
},
validate: function () {
var value = this.getValue();
var validators = this.schema.validators;
var getValidator = this.getValidator;
if (!validators) return null;
// Run through validators until an error is found
var error = null;
_.every(validators, function (validator) {
error = getValidator(validator)(value, {});
return !!error;
});
// Return error to be aggregated by list
return error ? error : null; // eslint-disable-line
},
focus: function () {
this.$('.js-button').focus();
},
blur: function () {
this.$('.js-button').blur();
},
getValue: function () {
var item = this.measurementModel;
if (item) {
return item.getValue();
} else if (this.value) {
return this.value;
}
},
setValue: function (value) {
var selectedModel = this.measurementModel;
if (selectedModel) {
this._renderButton(selectedModel);
}
this.value = value;
},
_renderButton: function (mdl) {
var button = this.$('.js-button');
var label = mdl.getName();
var $html = this.options.selectedItemTemplate({
label: label
});
button
.removeClass('is-empty')
.attr('title', label)
.html($html);
return button;
},
_renderLicense: function (mdl) {
var license = mdl.get('license');
var $license = this.$('.js-license');
$license
.removeClass('u-isHidden')
.find('span')
.text(license);
},
remove: function () {
this._popupManager && this._popupManager.destroy();
this._dialogView && this._dialogView.clean();
this._destroyBinds();
Backbone.Form.editors.Base.prototype.remove.call(this);
}
});

View File

@@ -0,0 +1,23 @@
<div class="CDB-InputText CDB-Text is-cursor js-button u-ellipsis
<% if (isDisabled) { %> is-disabled <% } %>
<% if (!label) { %> is-empty <% } %>
<% if (isNull) { %> is-empty <% } %>"
tabindex="0"
title="<%- title %>">
<% if (isLoading) { %>
<div class="CDB-LoaderIcon CDB-LoaderIcon--small is-dark u-iBlock">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<span class="u-lSpace u-secondaryTextColor"><%- _t('components.backbone-forms.select.loading') %></span>
<% } else { %>
<%- label %>
<% } %>
</div>
<div class="js-license CDB-Text CDB-FontSize-small u-altTextColor u-tSpace u-bSpace u-isHidden u-flex">
<a href="https://cartodb.github.io/bigmetadata/licenses.html" target="_blank">
<span class="u-tSpace u-bSpace DataObservatory-license"></span>
</a>
</div>

View File

@@ -0,0 +1,35 @@
var _ = require('underscore');
var CustomListMultiItemView = require('builder/components/custom-list/custom-list-multi-item-view');
var NAME = _.template('<%-name %> (<%- items %>)');
module.exports = CustomListMultiItemView.extend({
render: function () {
this.clearSubViews();
this.$el.empty();
var name = this.model.getName() == null ? 'null' : this.model.getName();
name = name.replace(/"/g, '');
this.$el.append(
this.options.template(
_.extend({
isSelected: this.model.get('selected'),
isDisabled: this.model.get('disabled'),
name: NAME({
name: name,
items: this.model.get('items')
}),
val: this.model.getValue(),
description: this.model.get('description')
})
)
);
this.$el
.attr('data-val', this.model.getValue())
.toggleClass('is-disabled', !!this.model.get('disabled'));
return this;
}
});

View File

@@ -0,0 +1,14 @@
<button type="button" class="CDB-ListDecoration-itemLink u-actionTextColor
<% if (isSelected) { %> is-selected <% } %>
" title="<%= name %>">
<div class="u-flex">
<div class="u-iBlock u-rSpace--m">
<input class="CDB-Checkbox js-input" type="checkbox" name="" value="" <% if (isSelected) { %>checked<% } %> <% if (isDisabled) { %>disabled<% } %> />
<span class="u-iBlock CDB-Checkbox-face"></span>
</div>
<div>
<div class="u-bSpace"><%= name %></div>
<div class="CDB-Size-small u-altTextColor"><%- description %></div>
</div>
</div>
</button>

View File

@@ -0,0 +1,11 @@
<div class="CDB-Box-modalHeader">
<ul class="CDB-Box-modalHeaderItem CDB-Box-modalHeaderItem--block CDB-Box-modalHeaderItem--paddingHorizontal">
<li class="CDB-ListDecoration-item CDB-ListDecoration-itemPadding--vertical CDB-Text CDB-Size-medium u-secondaryTextColor">
<button class="u-actionTextColor js-back u-rSpace">
<i class="CDB-IconFont CDB-IconFont-arrowPrev Size-large"></i>
</button>
<%- headerTitle%>
</li>
</ul>
</div>
<div class="js-content"></div>

Some files were not shown because too many files have changed in this diff Show More