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,31 @@
<div class="u-flex u-justifyCenter">
<div class="Modal-inner Modal-inner--grid u-flex u-justifyCenter">
<div class="Modal-icon">
<svg width="24px" height="25px" viewbox="521 436 24 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M524.5,440 L540.5,440 L540.5,460 L524.5,460 L524.5,440 Z M528.5,437 L536.5,437 L536.5,440 L528.5,440 L528.5,437 Z M522,440 L544,440 L522,440 Z M528.5,443.5 L528.5,455.5 L528.5,443.5 Z M532.5,443.5 L532.5,455.5 L532.5,443.5 Z M536.5,443.5 L536.5,455.5 L536.5,443.5 Z" id="Shape" stroke="#F19243" stroke-width="1" fill="none"/>
</svg>
</div>
<div>
<h2 class="CDB-Text CDB-Size-huge is-light u-bSpace--xl">
<%- _t('editor.widgets.delete.title', { name: name }) %>
</h2>
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('editor.widgets.delete.desc') %></p>
<ul class="Modal-listActions u-flex u-alignCenter">
<li class="Modal-listActionsitem">
<button class="CDB-Button CDB-Button--secondary CDB-Button--big js-cancel">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
<%- _t('editor.widgets.delete.cancel') %>
</span>
</button>
</li>
<li class="Modal-listActionsitem">
<button class="CDB-Button CDB-Button--primary CDB-Button--big js-confirm">
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
<%- _t('editor.widgets.delete.confirm') %>
</span>
</button>
</li>
</ul>
</div>
</div>
</div>

View File

@@ -0,0 +1,4 @@
<h2 class="Inline-editor">
<div class="CDB-Text CDB-Size-large u-ellipsis js-title Inline-editor-text"><%- title %></div>
<input type="text" name="text" class="Inline-editor-input Inline-editor-input--small CDB-Text CDB-InputText js-input" value="<%- title %>" readonly>
</h2>

View File

@@ -0,0 +1,164 @@
var Backbone = require('backbone');
var cdb = require('internal-carto.js');
var _ = require('underscore');
var moment = require('moment');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var QUERY_TEMPLATE = _.template('SELECT MAX(<%= column %>), MIN(<%= column %>) FROM (<%= table %>) __wrapped');
var STATUS = {
unavailable: 'unavailable',
unfetched: 'unfetched',
fetching: 'fetching',
fetched: 'fetched'
};
var DEFAULT_MAX_BUCKETS = 367;
var REQUIRED_OPTS = [
'configModel',
'querySchemaModel'
];
module.exports = Backbone.Model.extend({
defaults: {
status: STATUS.unfetched,
buckets: []
},
initialize: function (attrs, opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
if (!_.has(attrs, 'column')) {
throw new Error('Column is required');
}
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._initBinds();
if (this._querySchemaModel.get('status') === STATUS.fetched) {
this._queryData();
}
},
_initBinds: function () {
this.on('change:column', this._queryData, this);
this.listenTo(this._querySchemaModel, 'change:status', this._onQuerySchemaStatusChanged);
},
_onQuerySchemaStatusChanged: function () {
if (this._querySchemaModel.get('status') === STATUS.fetched) {
this._queryData();
}
},
_queryData: function () {
if (this._querySchemaModel.getColumnType(this.get('column')) !== 'date') {
return;
}
var query = QUERY_TEMPLATE({
column: this.get('column'),
table: this._querySchemaModel.get('query')
});
var callback = {
success: this._onQuerySuccess.bind(this),
error: this._onQueryError.bind(this)
};
this.set('status', STATUS.fetching);
this._SQL.execute(query, null, callback);
},
_onQuerySuccess: function (data) {
this.set('status', STATUS.fetched);
var row = data.rows && data.rows[0];
var max = row && row.max ? row.max : 0;
var min = row && row.min ? row.min : 0;
this._calculateBuckets(max, min);
},
_onQueryError: function () {
this.set('status', STATUS.unavailable);
},
_calculateDecadesDiff: function (start, end) {
var startYear = start.year();
var endYear = end.year();
var startDecade = Math.floor(startYear / 10);
var endDecade = Math.floor((endYear + 10) / 10);
return endDecade - startDecade;
},
_calculateBuckets: function (max, min) {
var end = moment(max).utc();
var start = moment(min).utc();
var BUCKET_INCREMENT = 1;
var buckets = [{
bins: end.diff(start, 'minutes', true),
val: 'minute',
label: 'Minutes'
}, {
bins: end.diff(start, 'hours', true),
val: 'hour',
label: 'Hours'
}, {
bins: end.diff(start, 'days', true),
val: 'day',
label: 'Days'
}, {
bins: end.diff(start, 'weeks', true),
val: 'week',
label: 'Weeks'
}, {
bins: end.diff(start, 'months', true),
val: 'month',
label: 'Months'
}, {
bins: end.diff(start, 'quarters', true),
val: 'quarter',
label: 'Quarters'
}, {
bins: end.diff(start, 'years', true),
val: 'year',
label: 'Years'
}, {
bins: this._calculateDecadesDiff(start, end),
val: 'decade',
label: 'Decades'
}];
var incrementedBuckets = _.map(buckets, function (bucket) {
var increment = bucket.val === 'decade'
? 0
: BUCKET_INCREMENT;
return _.extend(_.clone(bucket), {
bins: Math.ceil(bucket.bins) + increment
});
});
this.set('buckets', incrementedBuckets);
},
getFilteredBuckets: function (max) {
var limit = max || DEFAULT_MAX_BUCKETS;
var buckets = _.filter(this.get('buckets'), function (bucket) {
return bucket.bins <= limit;
});
return buckets;
},
getPreferredBucket: function (max) {
var sortedBuckets = _.sortBy(this.getFilteredBuckets(max), 'bins');
return sortedBuckets.length > 0 ? sortedBuckets[sortedBuckets.length - 1] : {};
}
});

View File

@@ -0,0 +1,11 @@
<svg width="56px" height="32px" viewBox="498 425 56 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Bolean" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(498.000000, 425.000000)">
<rect id="Rectangle-17" fill="#FFFFFF" x="0" y="0" width="56" height="32" rx="4"></rect>
<path d="M1,2.00174332 L1,2.00174332 L1,29.9982567 C1,30.5564876 1.4426521,31 1.99306965,31 L54.0069304,31 C54.5486548,31 55,30.5482532 55,29.9982567 L55,2.00174332 C55,1.44351239 54.5573479,1 54.0069304,1 L1.99306965,1 C1.45134516,1 1,1.4517468 1,2.00174332 L1,2.00174332 Z M0,2.00174332 C0,0.89621101 0.902308181,0 1.99306965,0 L54.0069304,0 C55.1076723,0 56,0.889261723 56,2.00174332 L56,29.9982567 C56,31.103789 55.0976918,32 54.0069304,32 L1.99306965,32 C0.892327676,32 0,31.1107383 0,29.9982567 L0,2.00174332 L0,2.00174332 Z" id="Rectangle-774" fill="#EEEEEE"></path>
<rect id="Rectangle-1876" fill="#EEEEEE" x="8" y="6" width="16" height="3"></rect>
<rect id="Rectangle-1876" fill="#EEEEEE" x="8" y="15" width="40" height="2" rx="1"></rect>
<rect id="Rectangle-1876-Copy" fill="#9DE0AD" x="8" y="15" width="31" height="2" rx="1"></rect>
<rect id="Rectangle-1876" fill="#EEEEEE" x="8" y="22" width="40" height="2" rx="1"></rect>
<rect id="Rectangle-1876-Copy" fill="#9DE0AD" x="8" y="22" width="11" height="2" rx="1"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,9 @@
<svg width="56px" height="32px" viewBox="0 0 56 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Graf" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle-17" fill="#FFFFFF" x="0" y="0" width="56" height="32" rx="4"></rect>
<path d="M1,2.00174332 L1,2.00174332 L1,29.9982567 C1,30.5564876 1.4426521,31 1.99306965,31 L54.0069304,31 C54.5486548,31 55,30.5482532 55,29.9982567 L55,2.00174332 C55,1.44351239 54.5573479,1 54.0069304,1 L1.99306965,1 C1.45134516,1 1,1.4517468 1,2.00174332 L1,2.00174332 Z M0,2.00174332 C0,0.89621101 0.902308181,0 1.99306965,0 L54.0069304,0 C55.1076723,0 56,0.889261723 56,2.00174332 L56,29.9982567 C56,31.103789 55.0976918,32 54.0069304,32 L1.99306965,32 C0.892327676,32 0,31.1107383 0,29.9982567 L0,2.00174332 L0,2.00174332 Z" id="Rectangle-774" fill="#EEEEEE"></path>
<rect id="Rectangle-1876" fill="#EEEEEE" x="8" y="6" width="16" height="3"></rect>
<rect id="Rectangle-53" fill="#9DE0AD" x="8" y="19" width="40" height="7"></rect>
<rect id="Rectangle-1876" fill="#EEEEEE" x="8" y="14" width="16" height="2"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,21 @@
<svg width="56px" height="32px" viewBox="498 425 56 32" version="1.1" mlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Bars" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(498.000000, 425.000000)">
<rect id="Rectangle-17" fill="#FFFFFF" x="0" y="0" width="56" height="32" rx="4"></rect>
<path d="M1,2.00174332 L1,2.00174332 L1,29.9982567 C1,30.5564876 1.4426521,31 1.99306965,31 L54.0069304,31 C54.5486548,31 55,30.5482532 55,29.9982567 L55,2.00174332 C55,1.44351239 54.5573479,1 54.0069304,1 L1.99306965,1 C1.45134516,1 1,1.4517468 1,2.00174332 L1,2.00174332 Z M0,2.00174332 C0,0.89621101 0.902308181,0 1.99306965,0 L54.0069304,0 C55.1076723,0 56,0.889261723 56,2.00174332 L56,29.9982567 C56,31.103789 55.0976918,32 54.0069304,32 L1.99306965,32 C0.892327676,32 0,31.1107383 0,29.9982567 L0,2.00174332 L0,2.00174332 Z" id="Rectangle-774" fill="#EEEEEE"></path>
<rect id="Bar" fill="#9DE0AD" transform="translate(36.000000, 23.000000) scale(-1, 1) translate(-36.000000, -23.000000) " x="35" y="22" width="2" height="2"></rect>
<rect id="Bar-Copy-6" fill="#9DE0AD" transform="translate(39.000000, 22.000000) scale(-1, 1) translate(-39.000000, -22.000000) " x="38" y="20" width="2" height="4"></rect>
<rect id="Bar-Copy-7" fill="#9DE0AD" transform="translate(42.000000, 21.500000) scale(-1, 1) translate(-42.000000, -21.500000) " x="41" y="19" width="2" height="5"></rect>
<rect id="Bar-Copy-8" fill="#9DE0AD" transform="translate(45.000000, 20.000000) scale(-1, 1) translate(-45.000000, -20.000000) " x="44" y="16" width="2" height="8"></rect>
<rect id="Bar-Copy-9" fill="#9DE0AD" transform="translate(48.000000, 21.500000) scale(-1, 1) translate(-48.000000, -21.500000) " x="47" y="19" width="2" height="5"></rect>
<rect id="Bar-Copy-2" fill="#9DE0AD" transform="translate(33.000000, 21.000000) scale(-1, 1) translate(-33.000000, -21.000000) " x="32" y="18" width="2" height="6"></rect>
<rect id="Bar-Copy-3" fill="#9DE0AD" transform="translate(30.000000, 22.500000) scale(-1, 1) translate(-30.000000, -22.500000) " x="29" y="21" width="2" height="3"></rect>
<rect id="Bar-Copy-4" fill="#9DE0AD" transform="translate(27.000000, 22.000000) scale(-1, 1) translate(-27.000000, -22.000000) " x="26" y="20" width="2" height="4"></rect>
<polygon id="Bar-Copy-5" fill="#9DE0AD" transform="translate(24.000000, 20.500000) scale(-1, 1) translate(-24.000000, -20.500000) " points="23 17 25 17 25 24 23 24"></polygon>
<rect id="Bar" fill="#9DE0AD" x="8" y="21" width="2" height="3"></rect>
<rect id="Bar-Copy-2" fill="#9DE0AD" x="11" y="15" width="2" height="9"></rect>
<rect id="Bar-Copy-3" fill="#9DE0AD" x="14" y="16" width="2" height="8"></rect>
<rect id="Bar-Copy-4" fill="#9DE0AD" x="17" y="19" width="2" height="5"></rect>
<polygon id="Bar-Copy-5" fill="#9DE0AD" points="20 16 22 16 22 24 20 24"></polygon>
<rect id="Rectangle-1876" fill="#EEEEEE" x="7" y="7" width="16" height="3"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,13 @@
<svg width="20px" height="18px" viewBox="10 11 20 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Group" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(10.000000, 11.000000)">
<rect id="Bar" fill="#A4DAB1" x="0" y="2" width="13" height="2"></rect>
<rect id="Bar" fill="#A4DAB1" x="0" y="9" width="10" height="2"></rect>
<rect id="Bar" fill="#A4DAB1" x="0" y="16" width="12" height="2"></rect>
<rect id="Bar" fill="#DDDDDD" x="0" y="7" width="7" height="1"></rect>
<rect id="Bar" fill="#DDDDDD" x="0" y="14" width="5" height="1"></rect>
<rect id="Bar" fill="#EEEEEE" x="13" y="2" width="7" height="2"></rect>
<rect id="Bar" fill="#EEEEEE" x="10" y="9" width="10" height="2"></rect>
<rect id="Bar" fill="#EEEEEE" x="12" y="16" width="8" height="2"></rect>
<rect id="Bar" fill="#DDDDDD" x="0" y="0" width="5" height="1"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1009 B

View File

@@ -0,0 +1,6 @@
<svg width="20px" height="8px" viewBox="0 0 20 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Group" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Bar-Copy-2" fill="#9DE0AD" x="0" y="4" width="20" height="4"></rect>
<rect id="Bar-Copy-3" fill="#DDDDDD" x="0" y="0" width="9" height="2"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 413 B

View File

@@ -0,0 +1,11 @@
<svg width="20px" height="18px" viewBox="10 11 20 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Bars" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(10.000000, 11.000000)">
<rect id="Bar" fill="#A4DAB1" x="0" y="10" width="2" height="8"></rect>
<rect id="Bar" fill="#A4DAB1" x="3" y="6" width="2" height="12"></rect>
<rect id="Bar" fill="#A4DAB1" x="6" y="2" width="2" height="16"></rect>
<rect id="Bar" fill="#A4DAB1" x="9" y="0" width="2" height="18"></rect>
<rect id="Bar" fill="#A4DAB1" x="12" y="2" width="2" height="16"></rect>
<rect id="Bar" fill="#A4DAB1" x="15" y="5" width="2" height="13"></rect>
<rect id="Bar" fill="#A4DAB1" x="18" y="8" width="2" height="10"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 850 B

View File

@@ -0,0 +1,19 @@
<svg width="21px" height="18px" viewBox="10 11 21 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Group-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(10.000000, 11.000000)">
<g id="Bars" transform="translate(0.000000, 8.000000)">
<rect id="Bar" fill="#F2CC8F" x="4" y="0" width="1" height="10"></rect>
<rect id="Bar" fill="#F2CC8F" x="6" y="1" width="1" height="9"></rect>
<rect id="Bar-Copy" fill="#F2CC8F" x="0" y="5" width="1" height="5"></rect>
<rect id="Bar" fill="#F2CC8F" x="8" y="2" width="1" height="8"></rect>
<rect id="Bar" fill="#F2CC8F" x="10" y="3" width="1" height="7"></rect>
<rect id="Bar" fill="#EEEEEE" x="12" y="4" width="1" height="6"></rect>
<rect id="Bar" fill="#EEEEEE" x="14" y="5" width="1" height="5"></rect>
<rect id="Bar" fill="#EEEEEE" x="16" y="6" width="1" height="4"></rect>
<rect id="Bar-Copy" fill="#F2CC8F" x="2" y="3" width="1" height="7"></rect>
<rect id="Bar" fill="#EEEEEE" x="18" y="7" width="1" height="3"></rect>
<rect id="Bar" fill="#EEEEEE" x="20" y="7" width="1" height="3"></rect>
</g>
<polygon id="Rectangle-316" fill="#979EA1" points="0 5 0 0 4 2.5"></polygon>
<rect id="Rectangle-1876" fill="#DDDDDD" x="8" y="1" width="12" height="3"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,21 @@
<svg width="56px" height="32px" viewBox="539 370 56 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Time-Series" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(539.000000, 370.000000)">
<rect id="Rectangle-17" fill="#FFFFFF" x="0" y="0" width="56" height="32" rx="4"></rect>
<path d="M1,2.00174332 L1,2.00174332 L1,29.9982567 C1,30.5564876 1.4426521,31 1.99306965,31 L54.0069304,31 C54.5486548,31 55,30.5482532 55,29.9982567 L55,2.00174332 C55,1.44351239 54.5573479,1 54.0069304,1 L1.99306965,1 C1.45134516,1 1,1.4517468 1,2.00174332 L1,2.00174332 Z M0,2.00174332 C0,0.89621101 0.902308181,0 1.99306965,0 L54.0069304,0 C55.1076723,0 56,0.889261723 56,2.00174332 L56,29.9982567 C56,31.103789 55.0976918,32 54.0069304,32 L1.99306965,32 C0.892327676,32 0,31.1107383 0,29.9982567 L0,2.00174332 L0,2.00174332 Z" id="Rectangle-774" fill="#EEEEEE"></path>
<rect id="Bar" fill="#DDDDDD" opacity="0.5" transform="translate(34.000000, 23.500000) scale(-1, 1) translate(-34.000000, -23.500000) " x="33" y="22" width="2" height="3"></rect>
<rect id="Bar-Copy-6" fill="#DDDDDD" opacity="0.5" transform="translate(37.000000, 24.000000) scale(-1, 1) translate(-37.000000, -24.000000) " x="36" y="23" width="2" height="2"></rect>
<rect id="Bar-Copy-7" fill="#DDDDDD" opacity="0.5" transform="translate(40.000000, 24.000000) scale(-1, 1) translate(-40.000000, -24.000000) " x="39" y="23" width="2" height="2"></rect>
<rect id="Bar-Copy-8" fill="#DDDDDD" opacity="0.5" transform="translate(43.000000, 24.500000) scale(-1, 1) translate(-43.000000, -24.500000) " x="42" y="24" width="2" height="1"></rect>
<rect id="Bar-Copy-9" fill="#DDDDDD" opacity="0.5" transform="translate(46.000000, 24.500000) scale(-1, 1) translate(-46.000000, -24.500000) " x="45" y="24" width="2" height="1"></rect>
<rect id="Bar-Copy-2" fill="#DDDDDD" opacity="0.5" transform="translate(31.000000, 23.500000) scale(-1, 1) translate(-31.000000, -23.500000) " x="30" y="22" width="2" height="3"></rect>
<rect id="Bar-Copy-3" fill="#DDDDDD" opacity="0.5" transform="translate(28.000000, 23.000000) scale(-1, 1) translate(-28.000000, -23.000000) " x="27" y="21" width="2" height="4"></rect>
<rect id="Bar-Copy-4" fill="#F2CC8F" transform="translate(25.000000, 23.000000) scale(-1, 1) translate(-25.000000, -23.000000) " x="24" y="21" width="2" height="4"></rect>
<polygon id="Bar-Copy-5" fill="#F2CC8F" transform="translate(22.000000, 22.500000) scale(-1, 1) translate(-22.000000, -22.500000) " points="21 20 23 20 23 25 21 25"></polygon>
<rect id="Bar-Copy-2" fill="#F2CC8F" x="9" y="24" width="2" height="1"></rect>
<rect id="Bar-Copy-3" fill="#F2CC8F" x="12" y="21" width="2" height="4"></rect>
<rect id="Bar-Copy-4" fill="#F2CC8F" x="15" y="18" width="2" height="7"></rect>
<polygon id="Bar-Copy-5" fill="#F2CC8F" points="18 19 20 19 20 25 18 25"></polygon>
<polygon id="Rectangle-316" fill="#979EA1" points="9 10 9 5 13 7.5"></polygon>
<rect id="Rectangle-1876" fill="#EEEEEE" x="16" y="6" width="16" height="3"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,143 @@
var CoreView = require('backbone/core-view');
var template = require('./widget-view.tpl');
var ContextMenuFactory = require('builder/components/context-menu-factory-view');
var InlineEditorView = require('builder/components/inline-editor/inline-editor-view');
var templateInlineEditor = require('./inline-editor.tpl');
var WidgetsService = require('./widgets-service');
var Analyses = require('builder/data/analyses');
var Router = require('builder/routes/router');
var widgetIconTemplateMap = {
category: require('./widget-icon-layer-category.tpl'),
histogram: require('./widget-icon-layer-histogram.tpl'),
formula: require('./widget-icon-layer-formula.tpl'),
'time-series': require('./widget-icon-layer-timeSeries.tpl')
};
/**
* View for an individual widget definition model.
*/
module.exports = CoreView.extend({
module: 'editor:widgets:widget-view',
tagName: 'li',
className: 'BlockList-item js-widgetItem',
events: {
'click': '_onEditWidget'
},
initialize: function (opts) {
if (!opts.layer) throw new Error('layer is required');
if (!opts.userActions) throw new Error('userActions is required');
this.layer = opts.layer;
this._userActions = opts.userActions;
this.stackLayoutModel = opts.stackLayoutModel;
this.listenTo(this.model, 'change', this.render);
this.listenToOnce(this.model, 'destroy', this._onDestroy);
},
render: function () {
this.clearSubViews();
this.$el.empty();
var widgetType = this.model.get('type');
var source = this.model.get('source');
var analysisNode = this.layer.findAnalysisDefinitionNodeModel(source);
var layerName = analysisNode.isSourceType()
? this.layer.getTableName()
: this.layer.getName();
this.$el.html(template({
widgetType: widgetType,
layerName: layerName,
sourceId: source,
sourceColor: analysisNode.getColor(),
sourceType: Analyses.short_title(analysisNode.get('type')),
isSourceType: analysisNode.isSourceType()
}));
this.$el.attr('data-model-cid', this.model.cid);
this._initViews();
return this;
},
_initViews: function () {
var widgetType = this.model.get('type');
this._inlineEditor = new InlineEditorView({
template: templateInlineEditor,
renderOptions: {
title: this.model.get('title')
},
onClick: this._onEditWidget.bind(this),
onEdit: this._renameWidget.bind(this)
});
this.$('.js-header').append(this._inlineEditor.render().el);
this.addView(this._inlineEditor);
var iconTemplate = widgetIconTemplateMap[widgetType];
if (!iconTemplate) {
console.log(widgetType + ' widget template not defined');
} else {
this.$('.js-widgetIcon').append(iconTemplate());
}
var menuItems = [{
label: _t('editor.widgets.options.rename'),
val: 'rename-widget',
action: this._onRenameWidget.bind(this)
}, {
label: _t('editor.widgets.options.edit'),
val: 'edit-widget',
action: this._onEditWidget.bind(this)
}, {
label: _t('editor.widgets.options.remove'),
val: 'delete-widget',
destructive: true,
action: this._confirmDeleteWidget.bind(this)
}];
this._contextMenuFactory = new ContextMenuFactory({
menuItems: menuItems
});
this.$('.js-context-menu').append(this._contextMenuFactory.render().el);
this.addView(this._contextMenuFactory);
},
_onRenameWidget: function () {
this._inlineEditor.edit();
},
_renameWidget: function () {
var newName = this._inlineEditor.getValue();
if (newName !== '' && newName !== this.model.get('title')) {
this.$('.js-title').text(newName).show();
this._inlineEditor.hide();
this.model.set({title: newName});
this._userActions.saveWidget(this.model);
}
},
_confirmDeleteWidget: function () {
WidgetsService.removeWidget(this.model);
},
_onEditWidget: function (event) {
event && event.stopPropagation();
WidgetsService.editWidget(this.model);
},
_onDestroy: function () {
Router.goToWidgetList();
this.clean();
}
});

View File

@@ -0,0 +1,35 @@
<div class="BlockList-dragIcon">
<div class="CDB-Shape">
<div class="CDB-Shape-rectsHandle is-small">
<div class="CDB-Shape-rectsHandleItem CDB-Shape-rectsHandleItem--grey is-first"></div>
<div class="CDB-Shape-rectsHandleItem CDB-Shape-rectsHandleItem--grey is-second"></div>
<div class="CDB-Shape-rectsHandleItem CDB-Shape-rectsHandleItem--grey is-third"></div>
</div>
</div>
</div>
<div class="BlockList-media u-rSpace--m js-widgetIcon">
</div>
<div class="BlockList-inner u-ellipsis">
<div class="BlockList-title u-bSpace js-context-menu">
<div class="BlockList-titleText js-header"></div>
</div>
<div class="u-flex u-alignCenter">
<span class="CDB-Text CDB-Size-small is-semibold u-upperCase" style="color: <%- sourceColor %>;">
<%- sourceId %>
</span>
<% if (!isSourceType) { %>
<span class="CDB-Text CDB-Size-small u-lSpace--s u-flex" style="color: <%- sourceColor %>;">
<i class="CDB-IconFont CDB-Size-small CDB-IconFont-ray"></i>
</span>
<% } %>
<span class="CDB-Text CDB-Size-small u-mainTextColor u-lSpace">
<%= sourceType %>
</span>
<span class="CDB-Text CDB-Size-small u-altTextColor u-ellipsis u-lSpace" title="<%= layerName %>">
<%= layerName %>
</span>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<h2 class="CDB-Text CDB-Size-huge is-light u-secondaryTextColor">
<%= body %>
</h2>

View File

@@ -0,0 +1,35 @@
<div class="FormPlaceholder-widget u-tSpace-m u-flex">
<div class="FormPlaceholder-widgetIcon u-rSpace--m"></div>
<div class="FormPlaceholder-widgetInner">
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--med u-tSpace-xl"></span>
</div>
</div>
<div class="FormPlaceholder-widget u-tSpace-m u-flex">
<div class="FormPlaceholder-widgetIcon u-rSpace--m"></div>
<div class="FormPlaceholder-widgetInner">
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--long u-tSpace-xl"></span>
</div>
</div>
<div class="FormPlaceholder-widget u-tSpace-m u-flex">
<div class="FormPlaceholder-widgetIcon u-rSpace--m"></div>
<div class="FormPlaceholder-widgetInner">
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--short u-tSpace-xl"></span>
</div>
</div>
<div class="FormPlaceholder-widget u-tSpace-m u-flex">
<div class="FormPlaceholder-widgetIcon u-rSpace--m"></div>
<div class="FormPlaceholder-widgetInner">
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
<span class="FormPlaceholder-title FormPlaceholder-size--short u-tSpace-xl"></span>
</div>
</div>

View File

@@ -0,0 +1,4 @@
<h2 class="Inline-editor">
<div class="CDB-Text CDB-Size-huge is-light u-ellipsis js-title Inline-editor-text"><%- title %></div>
<input type="text" name="text" class="Inline-editor-input Inline-editor-input--small CDB-Text CDB-InputText js-input" value="<%- title %>" readonly>
</h2>

View File

@@ -0,0 +1,123 @@
var _ = require('underscore');
var Backbone = require('backbone');
var WidgetDefinitionModel = require('builder/data/widget-definition-model');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var FillConstants = require('builder/components/form-components/_constants/_fill');
module.exports = Backbone.Model.extend({
initialize: function (attrs, options) {
var o = [
{
val: true,
label: _t('editor.widgets.widgets-form.style.yes')
}, {
val: false,
label: _t('editor.widgets.widgets-form.style.no')
}
];
this.schema = {
sync_on_bbox_change: {
type: 'Radio',
title: _t('editor.widgets.widgets-form.style.sync_on_bbox_change'),
options: o
}
};
},
_addAllStyleSchemaAttributes: function () {
var customType = this.get('type') === 'category' ? 'categories' : 'ramp';
var styleAttrs = {
widget_style_definition: {
type: 'Fill',
title: _t('editor.widgets.widgets-form.style.fill'),
options: [],
configModel: this._configModel,
modals: this._modals,
userModel: this._userModel,
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
color: {
hidePanes: [FillConstants.Panes.BY_VALUE],
disableOpacity: true
}
}
}
};
if (this.get('auto_style_allowed')) {
var editorAttrs = {
color: {
hidePanes: [FillConstants.Panes.FIXED]
}
};
if (customType === 'categories') {
editorAttrs.color.hideTabs = [
FillConstants.Tabs.BINS,
FillConstants.Tabs.QUANTIFICATION
];
}
styleAttrs = _.extend(styleAttrs, {
auto_style_definition: {
type: 'EnablerEditor',
title: '',
label: _t('editor.widgets.widgets-form.style.custom-colors'),
help: _t('editor.widgets.widgets-form.style.custom-help'),
editor: {
type: 'Fill',
title: '',
options: [this.get('column')],
dialogMode: DialogConstants.Mode.FLOAT,
query: 'query',
configModel: this._configModel,
modals: this._modals,
userModel: this._userModel,
editorAttrs: editorAttrs
}
}
});
} else {
styleAttrs = _.extend(styleAttrs, {
auto_style_definition: {
type: 'Text',
title: _t('editor.widgets.widgets-form.style.custom-colors'),
help: _t('editor.widgets.widgets-form.style.custom-disabled'),
disabled: true
}
});
}
this.schema = _.extend(this.schema, styleAttrs);
},
parse: function (r) {
var attrs = _.defaults(
{
sync_on_bbox_change: r.sync_on_bbox_change ? 'true' : 'false'
},
r
);
return attrs;
},
changeWidgetDefinitionModel: function (widgetDefinitionModel) {
var attrs = _.defaults(
{
sync_on_bbox_change: this.get('sync_on_bbox_change') === 'true'
},
this._prepareAttributesForWidgetDefinition()
);
if (attrs.auto_style_definition !== '' && _.isEmpty(attrs.auto_style_definition)) {
attrs.auto_style_definition = WidgetDefinitionModel.getDefaultAutoStyle(widgetDefinitionModel.get('type'), widgetDefinitionModel.get('column'));
}
widgetDefinitionModel.set(attrs);
},
_prepareAttributesForWidgetDefinition: function () {
return this.attributes;
}
});

View File

@@ -0,0 +1,122 @@
var _ = require('underscore');
var WidgetsFormBaseSchema = require('./widgets-form-base-schema-model');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var REQUIRED_OPTS = [
'columnOptionsFactory',
'modals',
'configModel',
'userModel'
];
module.exports = WidgetsFormBaseSchema.extend({
defaults: {
schema: {},
aggregate: {
attribute: '',
operator: 'count'
}
},
initialize: function (attrs, opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this.on('change:aggregate', this._updateAggregation, this);
this.on('change:column', this.updateSchema, this);
this._updateAggregation();
WidgetsFormBaseSchema.prototype.initialize.apply(this, arguments);
},
getFields: function () {
var fields = ['column', 'aggregate', 'prefix', 'suffix'];
return {
data: fields.join(','),
style: ['sync_on_bbox_change', 'widget_style_definition', 'auto_style_definition']
};
},
_updateAggregation: function () {
var aggregate = this.get('aggregate');
if (aggregate === undefined) {
this.set({
aggregation: 'count',
aggregation_column: ''
});
} else {
this.set({
aggregation_column: aggregate.attribute,
aggregation: aggregate.operator
});
}
this.updateSchema();
},
updateSchema: function () {
var columnOptions = this._columnOptionsFactory.create(this.get('column'));
var helpMsg = this._columnOptionsFactory.unavailableColumnsHelpMessage();
var aggregationOptions = _.filter(columnOptions, function (column) {
return column.type === 'number';
});
this.schema = _.extend(this.schema, {
column: {
type: 'Select',
title: _t('editor.widgets.widgets-form.data.aggregate-by'),
options: columnOptions,
help: helpMsg,
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
disabled: this._columnOptionsFactory.areColumnsUnavailable()
}
},
aggregate: {
type: 'Operators',
title: _t('editor.widgets.widgets-form.data.operation'),
options: aggregationOptions,
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
showSearch: false
}
},
suffix: {
type: 'EnablerEditor',
title: '',
label: _t('editor.widgets.widgets-form.data.suffix'),
editor: {
type: 'Text'
}
},
prefix: {
type: 'EnablerEditor',
title: '',
label: _t('editor.widgets.widgets-form.data.prefix'),
editor: {
type: 'Text'
}
}
});
this._addAllStyleSchemaAttributes();
},
canSave: function () {
var aggregation = this.get('aggregation');
var aggregationColumn = this.get('aggregation_column');
var canSave = false;
if (aggregation === 'count') {
canSave = true;
} else {
canSave = !!(aggregation && aggregationColumn);
}
return canSave;
}
});

View File

@@ -0,0 +1,102 @@
var _ = require('underscore');
var WidgetsFormBaseSchema = require('./widgets-form-base-schema-model');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
module.exports = WidgetsFormBaseSchema.extend({
defaults: {
schema: {},
aggregate: {
attribute: '',
operator: 'count'
}
},
initialize: function (attrs, opts) {
if (!opts.columnOptionsFactory) throw new Error('columnOptionsFactory is required');
this._columnOptionsFactory = opts.columnOptionsFactory;
this.listenTo(this, 'change:aggregate', this._updateAggregate);
this._updateAggregate();
WidgetsFormBaseSchema.prototype.initialize.apply(this, arguments);
},
parse: function (r) {
r.aggregate = {
attribute: r.column,
operator: r.operation
};
return r;
},
getFields: function () {
return {
data: 'aggregate,prefix,suffix,description',
style: 'sync_on_bbox_change'
};
},
_updateAggregate: function () {
var aggregate = this.get('aggregate');
this.set({
column: aggregate.attribute,
operation: aggregate.operator
});
this.updateSchema();
},
updateSchema: function () {
var columnOptions = this._columnOptionsFactory.create(this.get('column'), this._isNumberType);
this.schema = _.extend(this.schema, {
aggregate: {
type: 'Operators',
title: _t('editor.widgets.widgets-form.data.operation'),
options: columnOptions,
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
showSearch: false
}
},
suffix: {
type: 'EnablerEditor',
title: '',
label: _t('editor.widgets.widgets-form.data.suffix'),
editor: {
type: 'Text'
}
},
prefix: {
type: 'EnablerEditor',
title: '',
label: _t('editor.widgets.widgets-form.data.prefix'),
editor: {
type: 'Text'
}
},
description: {
type: 'EnablerEditor',
title: '',
label: _t('editor.widgets.widgets-form.style.description'),
editor: {
type: 'TextArea'
}
}
});
},
canSave: function () {
var column = this.get('column');
var operation = this.get('operation');
return operation === 'count' || !!column;
},
_isNumberType: function (m) {
return m.get('type') === 'number';
}
});

View File

@@ -0,0 +1,69 @@
var _ = require('underscore');
var WidgetsFormBaseSchema = require('./widgets-form-base-schema-model');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var NUMBER_TYPE = 'number';
var REQUIRED_OPTS = [
'columnOptionsFactory',
'modals',
'configModel',
'userModel'
];
module.exports = WidgetsFormBaseSchema.extend({
initialize: function (attrs, opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
WidgetsFormBaseSchema.prototype.initialize.apply(this, arguments);
},
getFields: function () {
return {
data: ['column', 'bins'],
style: ['sync_on_bbox_change', 'widget_style_definition', 'auto_style_definition']
};
},
updateSchema: function () {
var columnOptions = this._columnOptionsFactory.create(this.get('column'), this._isNumberType);
var helpMsg = this._columnOptionsFactory.unavailableColumnsHelpMessage();
this.schema = _.extend(this.schema, {
column: {
title: _t('editor.widgets.widgets-form.data.column'),
type: 'Select',
help: helpMsg,
options: columnOptions,
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
disabled: this._columnOptionsFactory.areColumnsUnavailable()
},
validators: [{
type: 'columnType',
columnsCollection: this._columnOptionsFactory._querySchemaModel.columnsCollection,
columnType: 'number'
}]
},
bins: {
title: _t('editor.widgets.widgets-form.data.bins'),
type: 'Number',
validators: ['required', {
type: 'interval',
min: 2,
max: 30
}]
}
});
this._addAllStyleSchemaAttributes();
},
canSave: function () {
return !!this.get('column');
},
_isNumberType: function (m) {
return m.get('type') === NUMBER_TYPE;
}
});

View File

@@ -0,0 +1,206 @@
var _ = require('underscore');
var WidgetsFormBaseSchema = require('./widgets-form-base-schema-model');
var TimeSeriesQueryModel = require('builder/editor/widgets/time-series-query-model');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var FillConstants = require('builder/components/form-components/_constants/_fill');
var timezones = require('builder/data/timezones');
var moment = require('moment');
var checkAndBuildOpts = require('builder/helpers/required-opts');
require('moment-timezone');
var REQUIRED_OPTS = [
'columnOptionsFactory',
'configModel',
'querySchemaModel'
];
module.exports = WidgetsFormBaseSchema.extend({
defaults: {
schema: {},
bins: 48,
timezone: '',
offset: 0
},
initialize: function (attrs, opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
WidgetsFormBaseSchema.prototype.initialize.apply(this, arguments);
this._timeSeriesQueryModel = new TimeSeriesQueryModel({
column: this.get('column')
}, {
configModel: this._configModel,
querySchemaModel: this._querySchemaModel
});
this._initBinds();
},
_initBinds: function () {
this.on('change:column', this._onColumnChanged, this);
this.listenTo(this._timeSeriesQueryModel, 'change:buckets', this.updateSchema);
},
getFields: function () {
var columnType = this._getColumnType();
var data = ['column'];
if (columnType === 'date') {
data.push('timezone', 'aggregation');
} else {
data.push('bins');
}
return {
data: data,
style: ['sync_on_bbox_change', 'widget_style_definition']
};
},
updateSchema: function () {
var columnOptions = this._columnOptionsFactory.create(this.get('column'), this._isNumberOrDateType.bind(this));
var helpMsg = this._columnOptionsFactory.unavailableColumnsHelpMessage();
this.schema = _.extend(this.schema, {
column: {
title: _t('editor.widgets.widgets-form.data.column'),
type: 'Select',
help: helpMsg,
options: columnOptions,
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
disabled: this._columnOptionsFactory.areColumnsUnavailable()
}
},
widget_style_definition: {
type: 'Fill',
title: _t('editor.widgets.widgets-form.style.fill'),
options: [],
dialogMode: DialogConstants.Mode.FLOAT,
editorAttrs: {
color: {
hidePanes: [FillConstants.Panes.BY_VALUE],
disableOpacity: true
}
}
}
});
var columnType = this._getColumnType();
if (columnType === 'date') {
var aggregationOptions = this._timeSeriesQueryModel.getFilteredBuckets();
var sortedTimezoneOptions = this._getSortedTimezoneOptions(timezones);
this.schema = _.extend(this.schema, {
aggregation: {
title: _t('editor.widgets.widgets-form.data.bins'),
type: 'Select',
placeholder: _t('editor.widgets.widgets-form.data.select-bucket'),
searchPlaceholder: _t('editor.widgets.widgets-form.data.search-by-bucket'),
options: aggregationOptions,
dialogMode: DialogConstants.Mode.FLOAT,
loading: _.isEmpty(aggregationOptions)
},
timezone: {
title: _t('editor.widgets.widgets-form.data.timezone'),
type: 'Select',
options: sortedTimezoneOptions,
dialogMode: DialogConstants.Mode.FLOAT
}
});
} else {
this.schema = _.extend(this.schema, {
bins: {
title: _t('editor.widgets.widgets-form.data.bins'),
type: 'Number',
validators: ['required', {
type: 'interval',
min: 0,
max: 256
}]
}
});
}
this.trigger('changeSchema');
},
canSave: function () {
return this.get('column');
},
_isDateType: function (model) {
return model.get('type') === 'date';
},
_isNumberType: function (model) {
return model.get('type') === 'number';
},
_isNumberOrDateType: function (model) {
return this._isDateType(model) || this._isNumberType(model);
},
_onColumnChanged: function () {
this.set({
aggregation: undefined
}, { silent: true });
this._timeSeriesQueryModel.set('column', this.get('column'));
this.set('column_type', this._getColumnType());
this.updateSchema();
},
_getColumnType: function () {
var column;
if (this._querySchemaModel.isFetched()) {
column = this._querySchemaModel.columnsCollection.findWhere({ name: this.get('column') });
}
return column && column.get('type');
},
_prepareAttributesForWidgetDefinition: function () {
var attrs = this.toJSON();
if (this._getColumnType() === 'date') {
attrs.bins = undefined;
attrs.offset = moment.tz(attrs.timezone).utcOffset() * 60;
} else {
attrs.aggregation = undefined;
attrs.timezone = undefined;
attrs.offset = undefined;
}
return attrs;
},
_getSortedTimezoneOptions: function (timezones) {
return _.chain(timezones)
.reduce(function (memo, tz) {
var name = tz.name;
memo.push({
label: tz.label,
name: name,
offset: moment.tz(name).utcOffset()
});
return memo;
}, [])
.sortBy('offset')
.reduce(function (memo, tz) {
var name = tz.name;
var timezone = tz.offset ? moment.tz(name).format('Z') : '';
memo.push({
label: '(GMT' + timezone + ') ' + tz.label,
val: name
});
return memo;
}, [])
.value();
}
});

View File

@@ -0,0 +1,133 @@
var CoreView = require('backbone/core-view');
var template = require('./widget-header.tpl');
var InlineEditorView = require('builder/components/inline-editor/inline-editor-view');
var VisTableModel = require('builder/data/visualization-table-model');
var templateInlineEditor = require('./inline-editor.tpl');
var ContextMenuFactory = require('builder/components/context-menu-factory-view');
var WidgetsService = require('builder/editor/widgets/widgets-service');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var analyses = require('builder/data/analyses');
var REQUIRED_OPTS = [
'layerDefinitionModel',
'userActions',
'stackLayoutModel',
'configModel'
];
module.exports = CoreView.extend({
events: {
'click .js-toggle-menu': '_onToggleContextMenuClicked'
},
initialize: function (opts) {
if (!this.model) throw new Error('model is required');
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._sourceNode = this._getSourceNode();
if (this._sourceNode) {
var tableName = this._sourceNode.get('table_name');
this._visTableModel = new VisTableModel({
id: tableName,
table: {
name: tableName
}
}, {
configModel: this._configModel
});
}
},
render: function () {
var widgetTitle = this.model.get('title');
var source = this.model.get('source');
var analysisNode = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(source);
var layerName = analysisNode.isSourceType()
? this._layerDefinitionModel.getTableName()
: this._layerDefinitionModel.getName();
this.$el.html(
template({
title: widgetTitle,
source: source,
color: this._layerDefinitionModel.get('color'),
layerName: layerName,
nodeTitle: analyses.short_title(analysisNode),
isSourceType: analysisNode.isSourceType(),
url: this._visTableModel ? this._visTableModel.datasetURL() : ''
})
);
this._initViews();
return this;
},
_initViews: function () {
var widgetTitle = this.model.get('title');
this._inlineEditor = new InlineEditorView({
template: templateInlineEditor,
renderOptions: {
title: widgetTitle
},
onEdit: this._renameWidget.bind(this)
});
this.$('.js-header').append(this._inlineEditor.render().el);
this.addView(this._inlineEditor);
var menuItems = [{
label: _t('editor.widgets.options.rename'),
val: 'rename-widget',
action: this._onRenameWidget.bind(this)
}, {
label: _t('editor.widgets.options.remove'),
val: 'delete-widget',
destructive: true,
action: this._confirmDeleteWidget.bind(this)
}];
this._contextMenuFactory = new ContextMenuFactory({
menuItems: menuItems
});
this.$('.js-context-menu').append(this._contextMenuFactory.render().el);
this.addView(this._contextMenuFactory);
},
_getSourceNode: function () {
var nodeModel = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();
var source;
if (nodeModel.get('type') === 'source') {
source = nodeModel;
} else {
var primarySource = nodeModel.getPrimarySource();
if (primarySource && primarySource.get('type') === 'source') {
source = primarySource;
}
}
return source;
},
_onRenameWidget: function () {
this._inlineEditor.edit();
},
_renameWidget: function () {
var newName = this._inlineEditor.getValue();
if (newName !== '' && newName !== this.model.get('title')) {
this.model.set({title: newName});
this._userActions.saveWidget(this.model);
this.$('.js-title').text(newName).show();
this._inlineEditor.hide();
}
},
_confirmDeleteWidget: function () {
WidgetsService.removeWidget(this.model);
}
});

View File

@@ -0,0 +1,39 @@
<ul class="Editor-breadcrumb">
<li class="Editor-breadcrumbItem CDB-Text CDB-Size-medium u-actionTextColor">
<button class="js-back">
<i class="CDB-IconFont CDB-IconFont-arrowPrev Size-large u-rSpace"></i>
<span class="Editor-breadcrumbLink"><%- _t('back') %></span>
</button>
</li>
<li class="Editor-breadcrumbItem CDB-Text CDB-Size-medium"><span class="Editor-breadcrumbSep"> / </span> <%- _t('editor.widgets.breadcrumb.widget-options') %></li>
</ul>
<div class="Editor-HeaderInfoEditor">
<div class="Editor-HeaderInfo-inner Editor-HeaderInfo-inner--wide">
<div class="Editor-HeaderInfo-title js-context-menu">
<div class="Editor-HeaderInfo-titleText js-header"></div>
</div>
<div class="Editor-HeaderInfo u-flex u-alignCenter">
<span class="CDB-Text CDB-Size-small is-semibold u-bSpace--s u-upperCase" style="color: <%- color %>;">
<%- source %>
</span>
<% if (!isSourceType) { %>
<span class="CDB-Text CDB-Size-small u-lSpace--s u-flex" style="color: <%- color %>;">
<i class="CDB-IconFont CDB-Size-small CDB-IconFont-ray"></i>
</span>
<% } %>
<span class="CDB-Text CDB-Size-small u-lSpace">
<%= nodeTitle %>
</span>
<span class="CDB-Text CDB-Size-small u-altTextColor u-ellipsis u-lSpace" title="<%= layerName %>">
<%= layerName %>
</span>
</div>
</div>
</div>

View File

@@ -0,0 +1,44 @@
/**
* Object to generate column options for a current state of a query schema model
*/
var F = function (querySchemaModel) {
this._querySchemaModel = querySchemaModel;
};
F.prototype.areColumnsUnavailable = function () {
var status = this._querySchemaModel.get('status');
return status === 'fetching' || status === 'unavailable';
};
F.prototype.unavailableColumnsHelpMessage = function () {
if (this._querySchemaModel.get('status') === 'unavailable') {
return _t('editor.widgets.widgets-form.data.columns-unavailable');
}
};
F.prototype.create = function (currentVal, columnFilter) {
columnFilter = columnFilter || function () {
return true;
};
switch (this._querySchemaModel.get('status')) {
case 'fetching':
return [{val: _t('editor.widgets.widgets-form.data.loading')}];
case 'unavailable':
return [{val: currentVal}];
default:
return this._querySchemaModel
.columnsCollection
.filter(columnFilter)
.map(function (m) {
var columnName = m.get('name');
return {
val: columnName,
label: columnName,
type: m.get('type')
};
});
}
};
module.exports = F;

View File

@@ -0,0 +1,77 @@
var CoreView = require('backbone/core-view');
var WidgetsFormView = require('./widgets-form-view');
var WidgetHeaderView = require('./widget-header.js');
var ScrollView = require('builder/components/scroll/scroll-view');
var Router = require('builder/routes/router');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'userActions',
'widgetDefinitionModel',
'modals',
'analysisDefinitionNodesCollection',
'layerDefinitionsCollection',
'stackLayoutModel',
'configModel',
'userModel'
];
/**
* View to render all necessary for the widget form
*/
module.exports = CoreView.extend({
events: {
'click .js-back': '_goBack'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
},
render: function () {
this.clearSubViews();
this._initViews();
return this;
},
_initViews: function () {
var self = this;
var nodeId = self._widgetDefinitionModel.get('source');
var analysisDefinitionNodeModel = self._analysisDefinitionNodesCollection.get(nodeId);
var header = new WidgetHeaderView({
layerDefinitionModel: this._layerDefinitionsCollection.get(this._widgetDefinitionModel.get('layer_id')),
model: this._widgetDefinitionModel,
modals: this._modals,
userActions: this._userActions,
stackLayoutModel: this._stackLayoutModel,
configModel: this._configModel
});
this.$el.append(header.render().$el);
this.addView(header);
var view = new ScrollView({
createContentView: function () {
return new WidgetsFormView({
userActions: self._userActions,
widgetDefinitionModel: self._widgetDefinitionModel,
querySchemaModel: analysisDefinitionNodeModel.querySchemaModel,
modals: self._modals,
configModel: self._configModel,
userModel: self._userModel
});
}
});
this.$el.append(view.render().$el);
this.addView(view);
},
_goBack: function () {
Router.goToPreviousRoute({
fallback: 'widgets'
});
}
});

View File

@@ -0,0 +1,71 @@
var _ = require('underscore');
var WidgetsFormColumnOptionsFactory = require('./widgets-form-column-options-factory');
var dataMap = {
category: {
labelTranslationKey: 'editor.widgets.widgets-form.type.category',
iconTemplate: require('builder/editor/widgets/widget-icon-category.tpl'),
Class: require('./schema/widgets-form-category-schema-model')
},
formula: {
labelTranslationKey: 'editor.widgets.widgets-form.type.formula',
iconTemplate: require('builder/editor/widgets/widget-icon-formula.tpl'),
Class: require('./schema/widgets-form-formula-schema-model'),
checkIfValid: function (querySchemaModel) {
return querySchemaModel.columnsCollection.any(function (m) {
return m.get('type') === 'number';
});
}
},
histogram: {
labelTranslationKey: 'editor.widgets.widgets-form.type.histogram',
iconTemplate: require('builder/editor/widgets/widget-icon-histogram.tpl'),
Class: require('./schema/widgets-form-histogram-schema-model'),
checkIfValid: function (querySchemaModel) {
return querySchemaModel.columnsCollection.any(function (m) {
return m.get('type') === 'number';
});
}
},
'time-series': {
labelTranslationKey: 'editor.widgets.widgets-form.type.time_series',
iconTemplate: require('builder/editor/widgets/widget-icon-timeSeries.tpl'),
Class: require('./schema/widgets-form-time-series-schema-model'),
checkIfValid: function (querySchemaModel) {
return querySchemaModel.columnsCollection.any(function (m) {
return m.get('type') === 'date' || m.get('type') === 'number';
});
}
}
};
module.exports = {
createWidgetFormModel: function (options) {
var widgetDefinitionModel = options.widgetDefinitionModel;
var widgetType = widgetDefinitionModel.get('type');
var Klass = dataMap[widgetType].Class;
return new Klass(widgetDefinitionModel.attributes, {
parse: true, // in case the raw attributes needs to be adapted to the expected form types, e.g. timestamp => Date
columnOptionsFactory: new WidgetsFormColumnOptionsFactory(options.querySchemaModel),
userModel: options.userModel,
modals: options.modals,
configModel: options.configModel,
querySchemaModel: options.querySchemaModel
});
},
getDataTypes: function (querySchemaModel) {
return _.reduce(dataMap, function (memo, val, key) {
if (val.checkIfValid ? val.checkIfValid(querySchemaModel) : true) {
memo.push({
iconTemplate: val.iconTemplate,
value: key,
label: _t(val.labelTranslationKey)
});
}
return memo;
}, []);
}
};

View File

@@ -0,0 +1,124 @@
var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var WidgetFormFactory = require('./widgets-form-factory');
var template = require('./widgets-form-fields.tpl');
require('builder/components/form-components/index');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var REQUIRED_OPTS = [
'userActions',
'widgetDefinitionModel',
'querySchemaModel',
'modals',
'configModel',
'userModel'
];
/**
* View of form to edit a widget definition's data
*
*/
module.exports = CoreView.extend({
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
var widgetOptions = {
widgetDefinitionModel: this._widgetDefinitionModel,
querySchemaModel: this._querySchemaModel,
modals: this._modals,
userModel: this._userModel,
configModel: this._configModel
};
this._widgetFormModel = WidgetFormFactory.createWidgetFormModel(widgetOptions);
this._widgetFormModel.updateSchema();
this._debounceSaveWidget = _.debounce(this._saveWidget.bind(this), 500);
this._initBinds();
},
render: function () {
this.clearSubViews();
this._removeForm();
this.$el.empty();
this._initViews();
return this;
},
_initBinds: function () {
this._widgetFormModel.bind('change:column change:aggregation changeSchema', this.render, this);
this._widgetFormModel.bind('change', this._onFormChange, this);
this.add_related_model(this._widgetFormModel);
this._widgetDefinitionModel.on('change:title', function (model, title) {
this._widgetFormModel.set({title: title}, {silent: true}); // silent to avoid sending the form
}, this);
this._widgetDefinitionModel.bind('change:auto_style_definition', this._onAutoStyleChanged, this);
this.add_related_model(this._widgetDefinitionModel);
},
_initViews: function () {
var model = this._widgetFormModel;
var fields = model.getFields();
this._widgetFormView = new Backbone.Form({
template: template,
templateData: {
dataFields: fields.data,
styleFields: fields.style
},
model: model
});
this._widgetFormView.bind('change', function () {
this.commit();
});
this.$el.append(this._widgetFormView.render().$el);
return this;
},
validateForm: function () {
return this._widgetFormView.validate();
},
_removeForm: function () {
// Backbone.Form removes the view with the following method
this._widgetFormView && this._widgetFormView.remove();
},
_onFormChange: function () {
if (this._widgetFormModel.canSave()) {
this._widgetFormModel.changeWidgetDefinitionModel(this._widgetDefinitionModel);
this._debounceSaveWidget();
}
},
_onAutoStyleChanged: function (widgetDefModel, changedAttrs) {
var previousAutoStyleDefinition = widgetDefModel.previous('auto_style_definition');
if (previousAutoStyleDefinition === '' && _.isEmpty(previousAutoStyleDefinition)) {
this._widgetFormModel.set({
auto_style_definition: widgetDefModel.get('auto_style_definition')
}, {
silent: true
});
if (!_.isEmpty(changedAttrs)) {
this.render();
}
}
},
_saveWidget: function () {
this._userActions.saveWidget(this._widgetDefinitionModel);
},
clean: function () {
this._removeForm();
CoreView.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,27 @@
<div class="Editor-HeaderInfo">
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
<div class="Editor-HeaderInfo-inner CDB-Text">
<div class="Editor-HeaderInfo-title u-bSpace--m">
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.widgets.widgets-form.data.title-label') %></h2>
</div>
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.widgets.widgets-form.data.description') %></p>
<div data-fields="<%- dataFields %>"></div>
</div>
</div>
<div class="CDB-HeaderInfo">
<div class="CDB-HeaderNumeration CDB-Text is-semibold u-rSpace--m">3</div>
<div class="Editor-HeaderInfo-inner CDB-Text">
<div class="Editor-HeaderInfo-title u-bSpace--m">
<h2 class="CDB-Text CDB-HeaderInfo-TitleText CDB-Size-large"><%- _t('editor.widgets.widgets-form.style.title-label') %></h2>
</div>
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.widgets.widgets-form.style.define') %></p>
<div data-fields="<%- styleFields %>"></div>
</div>
</div>

View File

@@ -0,0 +1,9 @@
<div class="Editor-HeaderInfo">
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
<div class="Editor-HeaderInfo-inner CDB-Text js-selector">
<div class="Editor-HeaderInfo-title u-bSpace--m">
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.widgets.widgets-form.type.title-label') %></h2>
</div>
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m js-highlight"><%- _t('editor.widgets.widgets-form.type.description') %></p>
</div>
</div>

View File

@@ -0,0 +1,125 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var CarouselFormView = require('builder/components/carousel-form-view');
var CarouselCollection = require('builder/components/custom-carousel/custom-carousel-collection');
var WidgetFormFactory = require('./widgets-form-factory');
var WidgetsFormFieldsView = require('./widgets-form-fields-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var loadingTemplate = require('builder/editor/layers/panel-loading-template.tpl');
var TIME_SERIES_TYPE = 'time-series';
var REQUIRED_OPTS = [
'userActions',
'widgetDefinitionModel',
'modals',
'querySchemaModel',
'configModel',
'userModel'
];
module.exports = CoreView.extend({
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._initBinds();
this._shouldFetchQuery();
},
render: function () {
this.clearSubViews();
this.$el.empty();
if (!this._querySchemaModel.isFetched()) {
this.$el.html(loadingTemplate());
} else {
this._renderCarousel();
this._renderForm();
}
return this;
},
_initBinds: function () {
this.listenTo(this._widgetDefinitionModel, 'change:type', this._renderFormAndValidate);
this.listenTo(this._querySchemaModel, 'change:status', this.render);
this.listenTo(this._querySchemaModel, 'change:query', this._shouldFetchQuery);
},
_shouldFetchQuery: function () {
if (this._querySchemaModel.shouldFetch()) {
this._querySchemaModel.fetch();
}
},
_renderCarousel: function () {
var filteredDataTypes = this._getFilteredDataTypes();
var carouselCollection = new CarouselCollection(
_.map(filteredDataTypes, function (type) {
return {
selected: this._widgetDefinitionModel.get('type') === type.value,
val: type.value,
label: type.label,
template: function () {
return (type.iconTemplate && type.iconTemplate({ makeItBig: true })) || type.value;
}
};
}, this)
);
carouselCollection.bind('change:selected', function (mdl) {
if (mdl.get('selected')) {
this._widgetDefinitionModel.changeType(mdl.getValue());
}
}, this);
var view = new CarouselFormView({
collection: carouselCollection,
template: require('./widgets-form-types.tpl')
});
this.addView(view);
this.$el.append(view.render().el);
},
_getFilteredDataTypes: function () {
var containsTimeSeries = false;
var modelCollection = this._widgetDefinitionModel.collection;
if (modelCollection) {
containsTimeSeries = modelCollection.any(function (model) {
return model.get('type') === TIME_SERIES_TYPE;
});
}
var filteredDataTypes = _.filter(WidgetFormFactory.getDataTypes(this._querySchemaModel), function (type) {
// Do not allow to change the widget to time-series if there is already a time-series widget
return (!containsTimeSeries ||
type.value !== TIME_SERIES_TYPE ||
this._widgetDefinitionModel.get('type') === TIME_SERIES_TYPE);
}, this);
return filteredDataTypes;
},
_renderFormAndValidate: function () {
this._renderForm();
if (this._formView.validateForm() === null) {
this._userActions.saveWidget(this._widgetDefinitionModel);
}
},
_renderForm: function () {
if (this._formView) {
this.removeView(this._formView);
this._formView.clean();
}
this._formView = new WidgetsFormFieldsView({
userActions: this._userActions,
widgetDefinitionModel: this._widgetDefinitionModel,
querySchemaModel: this._querySchemaModel,
modals: this._modals,
userModel: this._userModel,
configModel: this._configModel
});
this.addView(this._formView);
this.$el.append(this._formView.render().el);
}
});

View File

@@ -0,0 +1,5 @@
<h2 class="CDB-Text CDB-Size-huge is-light u-secondaryTextColor u-bSpace--xl"><%- _t('editor.widgets.widgets-form.placeholder-text') %></h2>
<button class="CDB-Button CDB-Button--dashed CDB-Button--wide CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase u-flex u-justifyCenter u-alignCenter js-add">
<span class="js-plus-icon u-flex u-justifyCenter u-alignCenter u-rSpace--m"></span>
<%- _t('editor.widgets.add-widget.label') %>
</button>

View File

@@ -0,0 +1,54 @@
var deleteWidgetConfirmationTemplate = require('./delete-widget-confirmation.tpl');
var ConfirmationView = require('builder/components/modals/confirmation/modal-confirmation-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var Router = require('builder/routes/router');
var REQUIRED_OPTS = [
'analysisDefinitionNodesCollection',
'editorModel',
'layerDefinitionsCollection',
'modals',
'userActions',
'widgetDefinitionsCollection'
];
var service = (function () {
return {
init: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
},
removeWidget: function (widgetDefinitionModel) {
if (widgetDefinitionModel) {
var self = this;
var widgetName = widgetDefinitionModel.get('title');
this._modalView = this._modals.create(function (modalModel) {
return new ConfirmationView({
modalModel: modalModel,
template: deleteWidgetConfirmationTemplate,
loadingTitle: _t('editor.widgets.delete.loading', { name: widgetName }),
renderOpts: {
name: widgetName
},
runAction: function () {
modalModel.destroy();
widgetDefinitionModel.destroy();
self._modalView.clean();
Router.goToWidgetList();
}
});
});
}
},
editWidget: function (model) {
this._editorModel.set('edition', false);
this._editorModel.trigger('cancelPreviousEditions');
Router.goToWidget(model.get('id'));
}
};
})();
module.exports = service;

View File

@@ -0,0 +1,218 @@
var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var EditorWidgetView = require('./widget-view');
var IconView = require('builder/components/icon/icon-view');
var template = require('./widgets-view.tpl');
var widgetPlaceholderTemplate = require('./widgets-placeholder.tpl');
var widgetsErrorTemplate = require('./widgets-content-error.tpl');
var widgetsNotReadyTemplate = require('./widgets-content-not-ready.tpl');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var AddWidgetsView = require('builder/components/modals/add-widgets/add-widgets-view');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
require('jquery-ui');
var STATES = {
loading: 'loading',
ready: 'ready'
};
var REQUIRED_OPTS = [
'userActions',
'analysisDefinitionNodesCollection',
'layerDefinitionsCollection',
'widgetDefinitionsCollection',
'userModel',
'stackLayoutModel',
'configModel',
'modals'
];
/**
* View to render widgets definitions overview
*/
module.exports = CoreView.extend({
module: 'editor:widgets:widgets-view',
events: {
'click .js-add': '_addWidget'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this.viewModel = new Backbone.Model({
state: STATES.loading
});
this._initViewState();
this._initBinds();
var callback = this._onAllQueryGeometryLoaded.bind(this);
this._layerDefinitionsCollection.loadAllQueryGeometryModels(callback);
},
render: function () {
this._destroySortable();
this.clearSubViews();
this.$el.empty();
this._initViews();
return this;
},
_initViewState: function () {
this._viewState = new Backbone.Model({
anyGeometryData: true
});
this._setViewState();
},
_initViews: function () {
if (this._isLoading()) {
this.$el.append(widgetsNotReadyTemplate());
return;
}
if (!this._viewState.get('anyGeometryData')) {
this._showNoGeometryData();
return;
}
if (this._widgetDefinitionsCollection.size() > 0) {
this.$el.append(template);
_.each(this._widgetDefinitionsCollection.sortBy('order'), this._addWidgetItem, this);
this._initSortable();
this._addTooltip();
this._renderPlusIcon();
return;
}
this.$el.append(widgetPlaceholderTemplate());
this._addTooltip();
this._renderPlusIcon();
},
_renderPlusIcon: function () {
var plusIcon = new IconView({
placeholder: this.$el.find('.js-plus-icon'),
icon: 'plus'
});
plusIcon.render();
this.addView(plusIcon);
},
_initBinds: function () {
this.listenTo(this._widgetDefinitionsCollection, 'destroy successAdd', this.render, this);
this.listenTo(this.viewModel, 'change:state', this.render, this);
this.listenTo(this._viewState, 'change', this.render);
},
_showNoGeometryData: function () {
this.$el.append(
widgetsErrorTemplate({
body: _t('editor.widgets.no-geometry-data')
})
);
},
_onAllQueryGeometryLoaded: function () {
this.viewModel.set('state', STATES.ready);
},
_addTooltip: function () {
var tooltip = new TipsyTooltipView({
el: this.$('.js-add'),
gravity: 'w',
title: function () {
return _t('editor.widgets.add-widget.tooltip');
},
offset: 8
});
this.addView(tooltip);
},
_isLoading: function () {
return this.viewModel.get('state') === STATES.loading;
},
_isReady: function () {
return this.viewModel.get('state') === STATES.ready;
},
_setViewState: function () {
this._layerDefinitionsCollection.isThereAnyGeometryData()
.then(function (anyGeometry) {
this._viewState.set('anyGeometryData', anyGeometry);
}.bind(this));
},
_initSortable: function () {
this.$('.js-widgets').sortable({
axis: 'y',
items: '> li.BlockList-item',
opacity: 0.8,
update: this._onSortableFinish.bind(this),
forcePlaceholderSize: false
}).disableSelection();
},
_destroySortable: function () {
if (this.$('.js-widgets').data('ui-sortable')) {
this.$('.js-widgets').sortable('destroy');
}
},
_onSortableFinish: function () {
var self = this;
this.$('.js-widgets > .js-widgetItem').each(function (index, item) {
var modelCid = $(item).data('model-cid');
var widgetDefModel = self._widgetDefinitionsCollection.get(modelCid);
widgetDefModel.set('order', index);
self._userActions.saveWidget(widgetDefModel);
});
},
_addWidget: function () {
if (this.$('.js-add').hasClass('is-disabled')) return;
var self = this;
this._modals.create(function (modalModel) {
return new AddWidgetsView({
modalModel: modalModel,
userModel: self._userModel,
userActions: self._userActions,
configModel: self._configModel,
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
layerDefinitionsCollection: self._layerDefinitionsCollection,
widgetDefinitionsCollection: self._widgetDefinitionsCollection
});
}, {
breadcrumbsEnabled: true
});
},
_addWidgetItem: function (model) {
var view = new EditorWidgetView({
model: model,
layer: this._layerDefinitionsCollection.get(model.get('layer_id')),
modals: this._modals,
userActions: this._userActions,
stackLayoutModel: this._stackLayoutModel
});
this.addView(view);
this.$('.js-widgets').append(view.render().el);
},
clean: function () {
this._destroySortable();
CoreView.prototype.clean.apply(this);
}
});

View File

@@ -0,0 +1,5 @@
<button class="CDB-Button CDB-Button--dashed CDB-Button--wide CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase u-bSpace--m u-flex u-justifyCenter u-alignCenter js-add">
<span class="js-plus-icon u-flex u-justifyCenter u-alignCenter u-rSpace--m"></span>
<%- _t('editor.widgets.add-widget.label') %>
</button>
<ul class="BlockList js-widgets"></ul>