Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
@@ -0,0 +1,51 @@
// Get the source node from an analysis node
function getSourceNode (nodeModel) {
var source;
if (nodeModel.get('type') === 'source') {
source = nodeModel;
} else {
var primarySource = nodeModel.getPrimarySource && nodeModel.getPrimarySource();
if (primarySource && primarySource.get('type') === 'source') {
source = primarySource;
} else {
source = getSourceNode(primarySource);
}
}
return source;
}
// Evaluates predicate for the analysis chain from nodeModel
function someNode (nodeModel, predicate) {
if (predicate(nodeModel)) {
return nodeModel;
}
if (nodeModel.get('type') !== 'source') {
const source = nodeModel.getPrimarySource && nodeModel.getPrimarySource();
if (source) {
return someNode(source, predicate);
}
}
return false;
}
function hasAnalysisType (type) {
return function (node) {
return node.get('type') === type;
};
}
const hasTradeArea = hasAnalysisType('trade-area');
const hasSQLFunction = hasAnalysisType('deprecated-sql-function');
module.exports = {
nodeHasTradeArea: node => someNode(node, hasTradeArea),
nodeHasSQLFunction: node => someNode(node, hasSQLFunction),
// These are defined outside because they're recursive
getSourceNode,
someNode
};
+34
View File
@@ -0,0 +1,34 @@
/**
* Just provide info about the current browser
*/
module.exports = function () {
var ua = navigator.userAgent;
var tem;
var M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return {
name: 'IE ',
version: (tem[1] || '')
};
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR\/(\d+)/);
if (tem != null) {
return {
name: 'Opera',
version: tem[1]
};
}
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) {
M.splice(1, 1, tem[1]);
}
return {
name: M[0],
version: M[1]
};
};
@@ -0,0 +1,55 @@
var _ = require('underscore');
var CartoColors = require('cartocolor');
var Tags = {
COLORBLIND: 'colorblind',
DIVERGING: 'diverging',
QUALITATIVE: 'qualitative',
QUANTITATIVE: 'quantitative'
};
module.exports = {
getColorRamps: function (colorsNumber) {
if (!colorsNumber) { throw new Error('Number of colors per ramp is required'); }
var rampColors = [];
_.each(CartoColors, function (color) {
if (color && color[colorsNumber]) {
rampColors.push(color[colorsNumber]);
}
});
return rampColors;
},
getColorRampsByTag: function (colorsNumber, tag) {
if (!colorsNumber) { throw new Error('Number of colors per ramp is required'); }
var rampColors = [];
_.each(CartoColors, function (color) {
if (color && color[colorsNumber] && _.contains(color.tags, tag)) {
rampColors.push(color[colorsNumber]);
}
});
return rampColors;
},
getColorblindRamps: function (colorsNumber) {
return this.getColorRampsByTag(colorsNumber, Tags.COLORBLIND);
},
getDivergingRamps: function (colorsNumber) {
return this.getColorRampsByTag(colorsNumber, Tags.DIVERGING);
},
getQualitativeRamps: function (colorsNumber) {
return this.getColorRampsByTag(colorsNumber, Tags.QUALITATIVE);
},
getQuantitativeRamps: function (colorsNumber) {
return this.getColorRampsByTag(colorsNumber, Tags.QUANTITATIVE);
}
};
@@ -0,0 +1,28 @@
module.exports = [
'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige',
'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown',
'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue',
'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod',
'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen',
'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen',
'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet',
'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick',
'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite',
'gold', 'goldenrod', 'gray', 'grey', 'green', 'greenyellow', 'honeydew',
'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender',
'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral',
'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightpink',
'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray',
'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta',
'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple',
'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise',
'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin',
'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered',
'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred',
'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue',
'purple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon',
'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue',
'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan',
'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white',
'whitesmoke', 'yellow', 'yellowgreen'
];
+29
View File
@@ -0,0 +1,29 @@
var _ = require('underscore');
var DEFAULT_ERROR_MSG = '';
/**
* Return error message when backend request fails
* It tries to get responseText > errors and error arrays, if not gets `statusText`.
*/
module.exports = function (e) {
if (!e) { throw new Error('error is required'); }
try {
var responseText = e.responseText && e.responseText.trim() && JSON.parse(e.responseText);
var errorMessage = e.statusText || DEFAULT_ERROR_MSG;
if (responseText) {
var errors = _.compact(
_.map(['errors', 'error'], function (type) {
return responseText[type] && responseText[type].join(', ');
})
);
errorMessage = errors.join(', ');
}
return errorMessage;
} catch (err) {
return DEFAULT_ERROR_MSG;
}
};
@@ -0,0 +1,7 @@
var track = function (error) {
if (window.trackJs) {
window.trackJs.track(error);
}
};
module.exports = track;
@@ -0,0 +1,40 @@
/**
* Fetch all query objects (querySchemaModel, queryGeometryModel, queryRowsCollection)
* if necessary
*/
module.exports = function (params) {
if (!params) throw new Error('all query objects are required');
if (!params.querySchemaModel) throw new Error('querySchemaModel is required');
if (!params.queryGeometryModel) throw new Error('queryGeometryModel is required');
if (!params.queryRowsCollection) throw new Error('queryRowsCollection is required');
function fetchModel (model) {
return new Promise(function (resolve, reject) {
var subscribeToFinalStatus = false;
if (model.shouldFetch()) {
model.fetch();
subscribeToFinalStatus = true;
} else if (!model.isInFinalStatus()) {
subscribeToFinalStatus = true;
} else {
resolve();
}
if (subscribeToFinalStatus) {
model.once('inFinalStatus', function () {
resolve();
});
}
});
}
var schemaModelPromise = fetchModel(params.querySchemaModel);
var geometryModelPromise = fetchModel(params.queryGeometryModel);
return Promise.all([schemaModelPromise, geometryModelPromise])
.then(function () {
return fetchModel(params.queryRowsCollection); // rows collection depends on schema
});
};
@@ -0,0 +1,21 @@
/**
* Send events to Google Analytics if it is available
* - https://developers.google.com/analytics/devguides/collection/analyticsjs/sending-hits
*
*/
var GAPusher = function (opts) {
var ga = window.ga;
opts = opts || {};
if (ga) {
ga(opts.eventName || 'send', {
hitType: opts.hitType,
eventCategory: opts.eventCategory,
eventAction: opts.eventAction,
eventLabel: opts.eventLabel
});
}
};
module.exports = GAPusher;
@@ -0,0 +1,69 @@
var _ = require('underscore');
var ACTIVE_LOCALE = window.ACTIVE_LOCALE;
var Locale = require('locale/index')[ACTIVE_LOCALE];
var LINK_TEMPLATE = _.template("<a href='<%- href %>'><%- link %></a>");
module.exports = {
/*
*
* Sometimes we need to include a link in a message. This function takes a key:
*
* {
* "body": "You are over platform's limits. Please %{link} to know more details",
* "link": "contact us",
* "href": "mailto:support@carto.com"
* }
*
* and return the message htmlfied with the link properly included
*/
linkify: function (key) {
// microtemplate function
// underscore doesn't allow change the template settings for one call
function t (s, d) {
for (var p in d) {
s = s.replace(new RegExp('%{' + p + '}', 'g'), d[p]);
}
return s;
}
var data = this.resolve(key);
if (!data) {
return void 0;
}
if (_.isString(data)) {
// we need an object with body, href, link
return data;
}
var message = data.body;
var link = data.link;
var href = data.href;
// If some data is missing or it doesn't need link
if (!message || !link || !href || !this.needLink(message)) {
return void 0;
}
return t(message, {
link: LINK_TEMPLATE({
link: link,
href: href
})
});
},
needLink: function (message) {
return message.indexOf('%{link}') >= 0;
},
// It returns the value or undefined for a given locale key
resolve: function (path) {
return path.split('.').reduce(function (prev, curr) {
return prev ? prev[curr] : void 0;
}, Locale);
}
};
+60
View File
@@ -0,0 +1,60 @@
var _ = require('underscore');
var DEFAULT_VALUES = {
top: 0,
left: 0
};
var MIN_GAP = 10;
var MIN_HEIGHT = 205;
var MIN_WIDTH = 244;
/**
* Would you like to know where to open a context menu?
* Just provide:
* @param {Object} options.parentView Parent element view
* @param {Number} options.posX Position X of the context menu
* @param {Number} options.posY Position Y of the context menu
* @param {Number} options.elementWidth (optional) Context menu width
* @param {Number} options.elementHeight (optional) Context menu height
* @param {Number} options.offsetX (optional) Context menu offsetX
* @param {Number} options.offsetY (optional) Context menu offsetY
*
* By default, it will try to positionate to the right (inside) and to
* the top (below).
*/
var MagicPositioner = function (params) {
if (!params.parentView) throw new Error('parentView within params is required');
if (!_.isNumber(params.posX)) throw new Error('posX within params is required');
if (!_.isNumber(params.posY)) throw new Error('posY within params is required');
var $parentView = params.parentView;
var parentViewHeight = $parentView.outerHeight();
var parentViewWidth = $parentView.outerWidth();
var posX = params.posX;
var posY = params.posY;
var offsetX = params.offsetX || 0;
var offsetY = params.offsetY || 0;
var elementWidth = params.elementWidth || MIN_WIDTH;
var elementHeight = params.elementHeight || MIN_HEIGHT;
var cssProps = DEFAULT_VALUES;
if ((posX - elementWidth) > MIN_GAP) {
cssProps.left = 'auto';
cssProps.right = (parentViewWidth - posX + offsetX) + 'px';
} else if ((posX + elementWidth) > elementWidth) {
cssProps.right = 'auto';
cssProps.left = (posX + offsetX) + 'px';
}
if ((posY + elementHeight) < parentViewHeight) {
cssProps.bottom = 'auto';
cssProps.top = (posY + offsetY) + 'px';
} else if ((posY + elementHeight) > parentViewHeight) {
cssProps.top = 'auto';
cssProps.bottom = (parentViewHeight - posY + offsetY) + 'px';
}
return cssProps;
};
module.exports = MagicPositioner;
+26
View File
@@ -0,0 +1,26 @@
var _ = require('underscore');
/**
* Mapcard preview url generator
*/
module.exports = {
urlForStaticMap: function (mapsApiTemplate, visualization, width, height) {
var formattedMapsApiTemplate = mapsApiTemplate.replace('{user}', visualization._permissionModel.get('owner').username);
var template = 'tpl_' + visualization.get('id').replace(/-/g, '_');
var imageUrl = formattedMapsApiTemplate + '/api/v1/map/static/named/' + template + '/' + width + '/' + height + '.png' + this._generateAuthTokensParams(visualization);
return imageUrl;
},
_generateAuthTokensParams: function (visualization) {
var authTokens = visualization.get('auth_tokens');
if (authTokens && authTokens.length > 0) {
return '?' + _.map(authTokens, function (t) {
return 'auth_token=' + t;
}).join('&');
} else {
return '';
}
}
};
@@ -0,0 +1,79 @@
var $ = require('jquery');
/**
* Check if Linux user used right/middle click at the time of the event
*
* @param ev {Event}
* @returns {boolean}
*/
function isLinuxMiddleOrRightClick (ev) {
return ev.which === 2 || ev.which === 3;
}
/**
* Check if Mac user used CMD key at the time of the event Mac user used CMD key at the time of the event.
*
* @param ev {Event}
* @returns {boolean}
*/
function isMacCmdKeyPressed (ev) {
return ev.metaKey;
}
function isCtrlKeyPressed (ev) {
return ev.ctrlKey;
}
/**
* Click handler for a cartodb.js view, to navigate event target's href URL through the view's router.navigate method.
*
* The default behavior is:
* Unless cmd/ctrl keys are pressed it will cancel the default link behavior and instead navigate to the URL set in the
* target's href attribute.
*
* Prerequisities:
* - view has a this.router instance.
*
* Example of how to use:
* - In a template:
* <a href="/some/uri" id="#my-link" ...
* <a href="/special/uri" id="#my-special-link" ...
*
* - In the view file:
* var navigateThroughRouter = require('--/--/common/view_helpers/navigateThroughRouter');
* module.exports = new CoreView.extend({
* events: {
* 'click a#my-link': navigateThroughRouter
* 'click a#my-special-link': this._myCustomRoute
* }
*
* _myCustomRoute: function(ev) {
* // Here you can do you custom logic before/after the routing, e.g.:
* console.log('before changing route');
* navigateThroughRouter.apply(this, arguments);
* console.log('after changing route');
* }
*
* @param ev {Event}
*/
module.exports = function (ev) {
// We always kill the default behaviour of the event, since container around view might have other click behavior.
// In case of a cmd/ctrl click by an user.
this.killEvent(ev);
var url = $(ev.target).closest('a').attr('href');
if (!url) {
return false;
}
if (!isLinuxMiddleOrRightClick(ev) && !isMacCmdKeyPressed(ev)) {
var routerModel = (this.routerModel || this.options.routerModel || this.options.router);
if (!routerModel) {
throw new Error('routerModel is required');
}
routerModel.navigate(url, { trigger: true });
} else if (isCtrlKeyPressed(ev) || isMacCmdKeyPressed(ev)) {
window.open(url, '_blank');
}
};
+355
View File
@@ -0,0 +1,355 @@
var carto = require('carto');
var torque = require('torque.js');
var Utils = require('builder/helpers/utils');
var _ = require('underscore');
var ERROR_REGEX = /.*:(\d+):(\d+)\s*(.*)/;
var CartoParser = function (cartocss) {
this.parse_env = null;
this.ruleset = null;
if (cartocss) {
this.parse(cartocss);
}
};
CartoParser.prototype = {
// value due to torque
RESERVED_VARIABLES: ['mapnik-geometry-type', 'points_density', 'points_count', 'src', 'value', 'agg_value', 'agg_value_density'],
parse: function (cartocss) {
this.parse_env = {
validation_data: false,
frames: [],
errors: [],
error: function (obj) {
obj.line = carto.Parser().extractErrorLine(cartocss, obj.index);
this.errors.push(obj);
}
};
var self = this;
var ruleset = null;
var defs = null;
try {
// set default reference
carto.tree.Reference.setData(carto.default_reference.version.latest);
ruleset = (new carto.Parser(this.parse_env)).parse(cartocss);
} catch (e) {
// add the style.mss string to match the response from the server
this.parse_env.errors = this.parseError(['style\.mss' + e.message]);
return;
}
if (ruleset) {
var existing = {};
var mapDef;
var symbolizers;
var i;
var j;
var r;
this.definitions = defs = ruleset.toList(this.parse_env);
for (i in defs) {
if (defs[i].elements.length > 0) {
if (defs[i].elements[0].value === 'Map') {
mapDef = defs.splice(i, 1)[0];
}
}
}
symbolizers = torque.cartocss_reference.version.latest.layer;
if (mapDef) {
mapDef.rules.forEach(function (r) {
var key = r.name;
var type;
var element;
if (!(key in symbolizers)) {
self.parse_env.error({
message: 'Rule ' + key + ' not allowed for Map.',
index: r.index
});
} else {
type = symbolizers[r.name].type;
element = r.value.value[0].value[0];
if (!self._checkValidType(element, type)) {
self.parse_env.error({
message: 'Expected type ' + type + '.',
index: r.index
});
}
}
});
}
defs = carto.inheritDefinitions(defs, this.parse_env);
defs = carto.sortStyles(defs, this.parse_env);
for (i in defs) {
for (j in defs[i]) {
r = defs[i][j];
if (r && r.toXML) {
r.toXML(this.parse_env, existing);
}
}
}
// toList uses parse_env.errors.message to put messages
if (this.parse_env.errors.message) {
_(this.parse_env.errors.message.split('\n')).each(function (m) {
self.parse_env.errors.push(m);
});
}
}
this.ruleset = ruleset;
return this;
},
_checkValidType: function (e, type) {
if (['number', 'float'].indexOf(type) > -1) {
return typeof e.value === 'number';
} else if (type === 'string') {
return e.value !== 'undefined' && typeof e.value === 'string';
} else if (type.constructor === Array) {
return type.indexOf(e.value) > -1 || e.value === 'linear';
} else if (type === 'color') {
return this._checkValidColor(e);
}
return true;
},
_checkValidColor: function (e) {
var expectedArguments = {rgb: 3, hsl: 3, rgba: 4, hsla: 4};
return typeof e.rgb !== 'undefined' || expectedArguments[e.name] === e.args;
},
/**
* gets an array of parse errors from windshaft
* and returns an array of {line:1, error: 'string'] with user friendly
* strings. Parses errors in format:
*
* 'style.mss:7:2 Invalid code: asdasdasda'
*
* it could also get already parsed objects so we return them directly without parsing them again
*/
parseError: function (errors) {
var parsedErrors = _.compact(errors).map(function (error) {
if (_.isObject(error)) return error;
var matchedError = error.match(ERROR_REGEX);
return matchedError
? { line: parseInt(matchedError[1], 10), message: matchedError[3] }
: { line: null, message: error };
});
// sort by line
parsedErrors.sort(function (a, b) { return a.line - b.line; });
parsedErrors = _.uniq(parsedErrors, true, function (a) { return a.line + a.message; });
return parsedErrors;
},
/**
* return the error list, empty if there were no errors
*/
errors: function () {
return this.parse_env ? this.parse_env.errors : [];
},
_colorsFromRule: function (rule) {
function searchRecursiveByType (v, t) {
var res = [];
var i;
var r;
for (i in v) {
if (v[i] instanceof t) {
res.push(v[i]);
} else if (typeof v[i] === 'object') {
r = searchRecursiveByType(v[i], t);
if (r.length) {
res = res.concat(r);
}
}
}
return res;
}
return searchRecursiveByType(rule.ev(this.parse_env), carto.tree.Color);
},
_varsFromRule: function (rule) {
function searchRecursiveByType (v, t) {
var res = [];
var i;
var r;
for (i in v) {
if (v[i] instanceof t) {
res.push(v[i]);
} else if (typeof v[i] === 'object') {
r = searchRecursiveByType(v[i], t);
if (r.length) {
res = res.concat(r);
}
}
}
return res;
}
return searchRecursiveByType(rule, carto.tree.Field);
},
/**
* Extract information from the carto using the provided method.
* */
_extract: function (method, extractVariables) {
var self = this;
var columns = [];
var definitions;
var def;
var d;
var r;
var f;
var k;
var rule;
var columnList;
var filter;
var filter_key;
if (this.ruleset) {
definitions = this.ruleset.toList(this.parse_env);
for (d in definitions) {
def = definitions[d];
if (def.filters) {
// extract from rules
for (r in def.rules) {
rule = def.rules[r];
columnList = method(this, rule);
columns = columns.concat(columnList);
}
if (extractVariables) {
for (f in def.filters) {
filter = def.filters[f];
for (k in filter) {
filter_key = filter[k];
if (filter_key.key && filter_key.key.value) {
columns.push(filter_key.key.value);
}
}
}
}
}
}
return _.reject(_.uniq(columns), function (v) {
return _.contains(self.RESERVED_VARIABLES, v);
});
}
},
/**
* return a list of colors used in cartocss
*/
colorsUsed: function (opt) {
// extraction method
var method = function (self, rule) {
var colors = self._colorsFromRule(rule);
return _.map(colors, function (color) {
return color.rgb;
});
};
var colors = this._extract(method, false);
if (opt && opt.mode === 'hex') {
colors = _.map(colors, function (color) {
return Utils.rgbToHex(color[0], color[1], color[2]);
});
}
return colors;
},
colorsUsedForLegend: function (opt) {
var rules = this.getDefaultRules();
if (rules['image-filters']) {
var method = function (self, rule) {
var imageFillRule = rules['image-filters'];
var colors = self._colorsFromRule(imageFillRule);
return _.map(colors, function (f) {
return f.rgb;
});
};
var colors = this._extract(method, true);
if (opt && opt.mode === 'hex') {
colors = _.map(colors, function (color) {
return Utils.rgbToHex(color[0], color[1], color[2]);
});
}
}
return _.map(colors, function (color) {
return { color: color };
});
},
/**
* return a list of variables used in cartocss
*/
variablesUsed: function () {
// extraction method
var method = function (self, rule) {
return _.map(self._varsFromRule(rule), function (f) {
return f.value;
});
};
return this._extract(method, true);
},
/**
* returns the default layer
*/
getDefaultRules: function () {
var rules = [];
var i;
var def;
var rulesMap = {};
for (i = 0; i < this.definitions.length; ++i) {
def = this.definitions[i];
// all zooms and default attachment so we don't get conditional variables
if (def.zoom === 8388607 && _.size(def.filters.filters) === 0 && def.attachment === '__default__') {
rules = rules.concat(def.rules);
}
}
for (i in rules) {
var rule = rules[i];
rulesMap[rule.name] = rule;
}
return rulesMap;
},
getRuleByName: function (definition, ruleName) {
if (!definition._rulesByName) {
var rulesMap = definition._rulesByName = {};
for (var r in definition.rules) {
var rule = definition.rules[r];
rulesMap[rule.name] = rule;
}
}
return definition._rulesByName[ruleName];
}
};
module.exports = CartoParser;
@@ -0,0 +1,10 @@
// Get coordinates from a GeoJSON
module.exports = function (geometry) {
try {
var geojson = JSON.parse(geometry);
return geojson.coordinates.join(', ');
} catch (e) {
return false;
}
};
@@ -0,0 +1,50 @@
var _ = require('underscore');
var Backbone = require('backbone');
var COUNT_SQL_TEMPLATE = 'select count(*) from (<%= subquery %>) st';
module.exports = Backbone.Collection.extend({
url: function () {
return this._configModel.getSqlApiUrl();
},
initialize: function (opts) {
opts = opts || {};
this._subquery = opts.subquery;
this._configModel = opts.configModel;
},
fetchCount: function (callback) {
var opts = {};
var count = -1;
opts.data = {
api_key: this._configModel.get('api_key'),
q: this._getCountSQL()
};
opts.method = this._httpMethod();
opts.error = function (coll, resp) {
callback && callback(count);
};
opts.success = function (coll, resp, options) {
if (resp.rows && resp.rows.length) {
count = resp.rows[0].count;
}
callback && callback(count);
};
return Backbone.Collection.prototype.fetch.call(this, opts);
},
_getCountSQL: function (excludeColumns) {
return _.template(COUNT_SQL_TEMPLATE)({
subquery: this._subquery
});
},
_httpMethod: function () {
return 'GET';
}
});
+12
View File
@@ -0,0 +1,12 @@
var _ = require('underscore');
module.exports = function checkAndBuildRequiredOpts (actualOpts, requiredOpts, context) {
if (requiredOpts === void 0) {
throw new Error('Opts are required');
}
_.each(requiredOpts, function (item) {
if (actualOpts === void 0 || actualOpts[item] === void 0) throw new Error(item + ' is required');
context['_' + item] = actualOpts[item];
}, context);
};
@@ -0,0 +1,95 @@
/* Check and perform a reset of the styles due to a change over a node definition model (if possible).
*
* In order to use and test this function in any view, we have removed this code
* from user-actions Class.
*
* forceStyleUpdate: it will reset the styles in any case, no matter the schema or the geometry.
* resetQueryReady: set as false ready property for query-schema-model and query-geometry-model
* in order to wait for a node change before checking if reset styles is necessary.
*
*/
module.exports = function (nodeDefModel, layerDefModel, forceStyleUpdate, resetQueryReady) {
if (!nodeDefModel) throw new Error('nodeDefModel is required');
if (!layerDefModel) throw new Error('layerDefModel is required');
var CHANGE_READY = 'change:ready';
var onQueryGeometryAndSchemaReady;
var styleModel = layerDefModel.styleModel;
var queryGeometryModel = nodeDefModel.queryGeometryModel;
var querySchemaModel = nodeDefModel.querySchemaModel;
onQueryGeometryAndSchemaReady = function () {
if (queryGeometryModel.get('ready') && querySchemaModel.get('ready')) {
queryGeometryModel.unbind(CHANGE_READY, onQueryGeometryAndSchemaReady);
querySchemaModel.unbind(CHANGE_READY, onQueryGeometryAndSchemaReady);
} else {
return; // wait until ready
}
var CHANGE_STATUS = 'change:status';
var saveDefaultStylesIfStillRelevant;
saveDefaultStylesIfStillRelevant = function () {
var applyStylesAndSave = function () {
var simpleGeometryType = queryGeometryModel.get('simple_geom');
if (simpleGeometryType) {
styleModel.setDefaultPropertiesByType('simple', simpleGeometryType);
} else {
styleModel.setDefaultPropertiesByType('none'); // fallback if there is no known geometry
}
layerDefModel.save();
};
var styleTypeApplied = styleModel.get('type');
if (queryGeometryModel.isDone() && querySchemaModel.isDone()) {
queryGeometryModel.unbind(CHANGE_STATUS, saveDefaultStylesIfStillRelevant);
querySchemaModel.unbind(CHANGE_STATUS, saveDefaultStylesIfStillRelevant);
} else {
return; // wait until ready
}
// If the geometry doesn't change and all columns used for style-def-model are still present + the styles are not custom
// we can reset the style form with the geometry provided
var haveDifferentSchema = querySchemaModel.hasDifferentSchemaThan(styleModel.getColumnsUsedForStyle());
if (
styleTypeApplied !== 'none' &&
!forceStyleUpdate && (
(!queryGeometryModel.hasChanged('simple_geom') && !haveDifferentSchema) ||
layerDefModel.get('cartocss_custom')
)
) {
return;
}
if ( // Only apply changes if:
(
layerDefModel.collection.contains(layerDefModel) && // layer still exist
layerDefModel.get('source') === nodeDefModel.id // node is still the head of layer
) || forceStyleUpdate // we force the style update
) {
applyStylesAndSave();
}
};
saveDefaultStylesIfStillRelevant();
if (!queryGeometryModel.isFetched()) {
queryGeometryModel.bind(CHANGE_STATUS, saveDefaultStylesIfStillRelevant);
}
if (!querySchemaModel.isFetched()) {
querySchemaModel.bind(CHANGE_STATUS, saveDefaultStylesIfStillRelevant);
}
};
if (resetQueryReady) {
queryGeometryModel.set('ready', false);
querySchemaModel.set('ready', false);
}
queryGeometryModel.bind(CHANGE_READY, onQueryGeometryAndSchemaReady);
querySchemaModel.bind(CHANGE_READY, onQueryGeometryAndSchemaReady);
onQueryGeometryAndSchemaReady();
};
+74
View File
@@ -0,0 +1,74 @@
/**
* Checks properties from a SQL
*/
var TableNameUtils = require('./table-name-utils');
module.exports = {
isSameQuery: function (originalQuery, customQuery) {
if (originalQuery == null || customQuery == null) { // eslint-disable-line
throw new Error('Needed parameters not provided');
}
var parseQuery = function (query) {
return query
.toLowerCase()
.replace(/\"/g, '') // Remove quoted things like "pepe".tableName
.replace(/;/g, '');
};
return parseQuery(originalQuery) === parseQuery(customQuery);
},
// return true if the sql query alters table schema in some way
altersSchema: function (sql) {
if (!sql) {
return false;
}
// Remove all line breaks in order to prevent
// search pattern problems
sql = this._removeLineBreaks(sql.trim());
return sql.search(/alter\s+[\w\."]+\s+/i) !== -1 ||
sql.search(/drop\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^vacuum\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^create\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^reindex\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^grant\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^revoke\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^cluster\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^comment\s+on\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^explain\s+[\w\.\"]+/i) !== -1;
},
// return true if the sql query alters table data
altersData: function (sql) {
if (!sql) {
return false;
}
// Remove all line breaks in order to prevent
// search pattern problems
sql = this._removeLineBreaks(sql.trim());
return this.altersSchema(sql) ||
sql.search(/^refresh\s+materialized\s+view\s+[\w\.\"]+/i) !== -1 ||
sql.search(/^truncate\s+[\w\.\"]+/i) !== -1 ||
sql.search(/insert\s+into/i) !== -1 ||
sql.search(/update\s+[\w\.\-"]+\s+.*set/i) !== -1 ||
sql.search(/delete\s+from/i) !== -1;
},
_removeLineBreaks: function (sql) {
return sql.replace(/\r?\n|\r/g, ' ');
},
getDefaultSQL: function (tableName, userName, inOrganization) {
return this.getDefaultSQLFromTableName(TableNameUtils.getQualifiedTableName(tableName, userName, inOrganization));
},
getDefaultSQLFromTableName: function (tableName) {
return 'SELECT * FROM ' + tableName;
}
};
@@ -0,0 +1,86 @@
var _ = require('underscore');
var ParserCSS = require('builder/helpers/parser-css');
var LegendColorHelper = require('builder/editor/layers/layer-content-views/legend/form/legend-color-helper');
module.exports = {
getStyleAttrs: function (styleModel) {
if (!styleModel) return;
var fill = styleModel.get('fill');
var stroke = styleModel.get('stroke');
if (!fill && !stroke || _.isEmpty(fill) && _.isEmpty(stroke)) return;
return {
fill: fill,
stroke: stroke
};
},
getColorAttribute: function (styleModel) {
var color = this.getColor(styleModel);
return color && color.attribute;
},
getSizeAttribute: function (styleModel) {
var size = this.getSize(styleModel);
return size && size.attribute;
},
getColor: function (styleModel) {
var style = this.getStyleAttrs(styleModel);
var color = style ? (style.fill && style.fill.color || style.stroke && style.stroke.color) : null;
return color;
},
getSize: function (styleModel) {
var style = this.getStyleAttrs(styleModel);
var size = style ? (style.fill && style.fill.size || style.stroke && style.stroke.size) : null;
return size;
},
getColorsFromRange: function (styleModel) {
var color = this.getColor(styleModel);
if (!(color && color.range)) { return []; }
return color.range.map(function (v, index) {
return { color: v };
});
},
getHeatmapColors: function (layerDefinitionModel) {
var changed = layerDefinitionModel.styleModel && layerDefinitionModel.styleModel.hasChanged();
if (changed) {
return this.getColorsFromRange(layerDefinitionModel.styleModel);
}
var content = layerDefinitionModel.cartocssModel.get('content');
var parser = new ParserCSS(content);
return parser.colorsUsedForLegend({ mode: 'hex' });
},
getStyleCategories: function (styleModel) {
var color = this.getColor(styleModel);
if (!color) { return []; }
if (color.range) {
return color.range.map(function (v, index) {
return {
color: v,
title: color.domain && LegendColorHelper.unquoteColor(color.domain[index]) || _t('editor.legend.legend-form.others'),
icon: color.images && color.images[index] || ''
};
});
}
if (color.fixed) {
return [{
color: color.fixed,
icon: color.image || '',
title: ''
}];
}
return [];
}
};
@@ -0,0 +1,16 @@
module.exports = function () {
var stylesWithoutAutostyles;
return function (layerDefModel) {
var isAutoStyleActive = !!layerDefModel.get('autoStyle');
if (!isAutoStyleActive) {
stylesWithoutAutostyles = {
previousCartoCSS: layerDefModel.get('cartocss'),
previousCartoCSSCustom: layerDefModel.attributes.cartocss_custom
};
}
return stylesWithoutAutostyles;
};
};
+55
View File
@@ -0,0 +1,55 @@
/**
* Functions to work with SQL table names
*/
module.exports = {
getUnqualifiedName: function (tablename) {
if (!tablename) return null;
var tk = tablename.split('.');
if (tk.length === 2) {
return this._getUnquotedName(tk[1]);
}
return this._getUnquotedName(tablename);
},
getUsername: function (tablename) {
if (!tablename) return null;
var tk = tablename.split('.');
if (tk.length === 2) {
return this._getUnquotedName(tk[0]);
}
return '';
},
_getUnquotedName: function (tablename) {
return tablename && tablename.replace(/"/g, '');
},
_quoteIfNeeded: function (name) {
var VALID_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_$]*$/;
name = this._getUnquotedName(name);
if (!VALID_IDENTIFIER.test(name)) {
name = '"' + name + '"';
}
return name;
},
getQualifiedTableName: function (tableName, userName, inOrganization) {
var schemaPrefix = (inOrganization && userName) ? this._quoteIfNeeded(userName) + '.' : '';
return schemaPrefix + this._quoteIfNeeded(this.getUnqualifiedName(tableName));
},
isSameTableName: function (firstTableName, secondTableName, ownerUsername) {
var firstParts = this._getTableNameParts(firstTableName, ownerUsername);
var secondParts = this._getTableNameParts(secondTableName, ownerUsername);
return firstParts[0] === secondParts[0] && firstParts[1] === secondParts[1];
},
_getTableNameParts: function (tableName, ownerUsername) {
var table = this.getUnqualifiedName(tableName);
var username = this.getUsername(tableName) || ownerUsername;
return [username, table];
}
};
+357
View File
@@ -0,0 +1,357 @@
var _ = require('underscore');
var cdb = require('internal-carto.js');
/*
* Util functions
*/
module.exports = {
/*
* Simple regex to check if string is an url/ftp
* input -> string with input text (example: 'https://carto.com')
*
* return -> true
*/
isURL: function (input) {
var urlregex = /^((http|https|ftp)\:\/\/)/g;
if (input) {
return urlregex.test(input);
} else {
return false;
}
},
/*
* check if string is blank (i.e.: str === "")
* input -> string with input text
*
* @return a boolean
*/
isBlank: function (str) {
return (!str || /^\s*$/.test(str));
},
/*
* Transform bytes to a readable format, like MB, GB
* input -> 34234244
*
* return -> 3 MB
*/
readablizeBytes: function (bytes, round) {
if (!bytes || isNaN(bytes)) {
return 0;
}
var s = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'];
var e = Math.floor(Math.log(bytes) / Math.log(1024));
var value = (bytes / Math.pow(1024, Math.floor(e))).toFixed(2);
if (round) {
value = parseInt(value, 10);
}
return value + ' ' + s[e];
},
/**
* Convert long numbers to
* readizable numbers.
*
*/
readizableNumber: function (num) {
if (num >= 1000000000) return (num / 1000000000).toFixed(1) + 'G';
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return num;
},
/*
* formatNumber: adds thousands separators
* @return a string
*
*/
formatNumber: function (x) {
if (!x) return '0';
var parts = x.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
},
/**
* Similar to _.result, but also allows passing arbitrary arguments to the property if it's function.
* This makes code more terse when one just wants to use a value if it's available, no if-checks required.
*
* @example Expected output
* model.set('something', 'yay');
* cdb.Utils.result(model, 'get', 'something') // => 'yay'
* cdb.Utils.result(model, 'nonexisting', 'else') // => undefined
* cdb.Utils.result(undefinedVar, 'get') // => null
*
* @example Of usage
* return cdb.Utils.result(model, 'get', 'mightNotExist') === 'OK'
*
* @param {*} maybeFn
* @return {*} Result from called maybeFn if a function, null otherwise
*/
result: function (object, property) {
if (object == null) return null;
var value = object[property];
return _.isFunction(value) ? value.apply(object, Array.prototype.slice.call(arguments, 2)) : value;
},
/**
* Get the extension of a string
*
*/
getFileExtension: function (str) {
if (!str) return '';
return str.substr(str.lastIndexOf('.') + 1);
},
/**
* Add leading zeros to numbers
*
*/
pad: function (num, size) {
var s = num + '';
while (s.length < size) s = '0' + s;
return s;
},
formatDate: function (opts) {
if (!opts.time) throw new Error();
if (opts.month === undefined) throw new Error();
if (opts.year === undefined) throw new Error();
if (!opts.day) throw new Error();
// Month in Date format should be specified as an index
var month = parseInt(opts.month + 1, 10);
var padZero = function (digit) {
digit = String(digit);
if (digit < 10 && digit.length === 1) {
return '0' + digit;
}
return digit;
};
return '' +
opts.year + '-' +
padZero(month) + '-' +
padZero(opts.day) + 'T' +
opts.time + 'Z';
// Not adding any info about timezone
},
/*
* rgbToHex
*
*/
rgbToHex: function (r, g, b) {
function componentToHex (c) {
var hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b);
},
/*
* Returns true if the hex color passed as an input is valid
* input -> hex (#FF00FF)
* output -> true
*
* @return a boolean
*/
isValidHex: function (hex) {
return !!hex.match(/(^#?[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i);
},
/*
* Returns #FFFFFF in case the input is not a valid HEX number
*/
sanitizeHex: function (hex) {
if (!this.isValidHex(hex)) {
return '#FFFFFF';
}
return hex;
},
/*
* Transforms an hex color into its RGB representation
* input -> hex (#FF00FF)
* output -> { r: 255, g: 0, b: 255 }
*
* @return a hash
*/
hexToRGB: function (hex) {
if (!hex) {
hex = '#FFFFFF';
}
var shortRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shortRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
},
/*
* Transforms an hex color an a opacity value to a rgba string
* input -> hex (#FF00FF) and opacity (0.4)
* output -> 'rgba(255,0,255,0.4)'
*
* @return a string
*/
hexToRGBA: function (hex, opacity) {
function roundToTwo (num) {
return +(Math.round(num + 'e+2') + 'e-2');
}
var rgb = this.hexToRGB(hex);
opacity = opacity != null ? roundToTwo(opacity) : 1;
if (rgb) {
return 'rgba(' + [rgb.r, rgb.g, rgb.b, opacity].join(', ') + ')';
} else {
return hex;
}
},
/*
* Strip html tags from a value.
* input -> string with input text (example: '<a href="#whoknows">Jamon</a> </br> <p>Vamos</p>')
* allowed -> allowed html tags in the result (example: '<a>')
*
* return -> '<a href="#whoknows">Jamon</a> Vamos'
*/
stripHTML: function (input, allowed) {
allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
if (!input || (typeof input !== 'string')) return '';
return input.replace(tags, function ($0, $1) {
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
});
},
/**
* Replace all HTML characters to display HTML content in HTML
* <h1>Hello</h1> -> &lt;h1&gt;Hello&lt;/h1&gt; displays '<h1>Hello</h1>'
*
*/
escapeHTML: function (str) {
return _.escape(str);
},
/**
* Remove all non-common characters like spaces, quotes, accents, etc. and
* joins strings with underscore symbols
*
*/
sanitizeString: function (str) {
return str.replace(/[^a-z0-9\s]/gi, '').replace(/[_\s]/g, '_');
},
/**
* Returns true is value is a valid number. Based on jQuery isNumeric
*/
isNumeric: function (value) {
return !isNaN(parseFloat(value)) && isFinite(value);
},
/**
* Formats a number that to include up to 2 decimal positions if less than 10
* and up to 1 if greater than 10.
*/
formatDecimalPositions: function (value) {
// we are using here the unary operator because parseInt fails to handle exponential number
var converted = +value;
var p = 0;
var abs_v;
if (isNaN(converted) || converted === 0) {
return value;
}
abs_v = Math.abs(converted);
if (abs_v > 10) {
p = 1;
} else if (abs_v > 0.01) {
p = Math.min(Math.ceil(Math.abs(Math.log(abs_v) / Math.log(10))) + 2, 2);
}
value = value.toFixed(p);
var m = value.match(/(\.0+)$/);
if (m) {
value = value.replace(m[0], '');
}
return value;
},
/**
* Remove new lines from string
*
*/
removeNewLines: function (str) {
return str.replace(/(\r\n|\n|\r)/gm, '');
},
replaceLastSpaceWithNbsp: function (string) {
var nbsp = '\u00a0';
var lastSpace = string.lastIndexOf(' ');
return string.substr(0, lastSpace) + nbsp + string.substr(lastSpace + 1);
},
/**
* Returns true if the string ends with provided suffix
*/
endsWith: function (str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
},
cloneObject: function (obj) {
if (!obj) {
return obj;
}
return JSON.parse(JSON.stringify(obj));
},
/**
* Capitalize first letter of string
*
*/
capitalize: function (str) {
if (!str) {
return str;
}
return str.charAt(0).toUpperCase() + str.slice(1);
},
sanitizeHtml: function (text) {
return cdb.core.sanitize.sanitize(text || '');
},
/**
* Returns true if the value is present
*
*/
hasValue: function (value) {
return value !== null &&
value !== undefined &&
value !== '' &&
!(typeof value === 'number' && isNaN(value));
},
isValidEmail: function (email) {
const EMAIL_REGEX = /^([^@]+)@([^@]+)\.([^@\.]+)$/i;
return EMAIL_REGEX.test(email);
}
};