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('components.table.rows.destroy.title', { cartodb_id: cartodb_id }) %>
</h2>
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('components.table.rows.destroy.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('components.table.rows.destroy.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('components.table.rows.destroy.confirm') %>
</span>
</button>
</li>
</ul>
</div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
<td class="Table-cellItem" data-attribute="<%- columnName %>" title="<%- value %>" data-clipboard-text='<%- value %>'>
<div class="
Table-cell u-flex u-justifySpace
<%- columnName === 'cartodb_id' || (type === 'geometry' && geometry !== 'point') ? 'Table-cell--short' : '' %>
">
<!--
WARNING: .fs-hide class excludes this element, which might
contain sensitive data, from FullStory recordings.
Do not remove it unless we are no longer using FullStory!
More info:
https://help.fullstory.com/technical-questions/exclude-elements
-->
<p class="
fs-hide
CDB-Text CDB-Size-medium
u-ellipsis u-rSpace--xl
<%- type === 'number' && columnName !== 'cartodb_id' ? 'is-number' : '' %>
<%- value === null ? 'is-null' : '' %>
<%- columnName === 'cartodb_id' ? 'is-cartodbId' : '' %>
js-value
">
<%- value === null ? 'null' : formattedValue %>
</p>
<% if (columnName !== 'cartodb_id') { %>
<button class="CDB-Shape-threePoints is-blue is-small Table-cellItemOptions js-cellOptions">
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
</button>
<% } %>
</div>
</td>

View File

@@ -0,0 +1,95 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var cellTemplate = require('./table-body-cell.tpl');
var pointGeojsonParser = require('builder/helpers/point-geojson-parser');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var FORMATTED_TYPE_VALUES = {
'geometry': pointGeojsonParser
};
var REQUIRED_OPTS = [
'columnsCollection',
'tableViewModel',
'canHideColumns'
];
/*
* Table body row view
*/
module.exports = CoreView.extend({
className: 'Table-row',
tagName: 'tr',
options: {
simpleGeometry: ''
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this.el.setAttribute('data-model', this.model.cid);
this._initBinds();
},
render: function () {
this.$el.empty();
this._generateRowsHTML();
return this;
},
_initBinds: function () {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
_generateRowsHTML: function () {
var r = [];
for (var i = 0, l = this._columnsCollection.size(); i < l; i++) {
var model = this._columnsCollection.at(i);
var columnName = model.get('name');
if (!this._tableViewModel.isCustomQueryApplied() && columnName === 'the_geom_webmercator') {
continue;
}
if (this._canHideColumns && columnName === 'center' && model.isGeometryColumn()) {
continue;
}
r[i] = this._generateCellHTML(columnName);
}
this.el.innerHTML = r.join('');
},
_generateCellHTML: function (columnName) {
var simpleGeometry = this.options.simpleGeometry;
var columnModel = _.first(this._columnsCollection.where({ name: columnName }));
var columnType = columnModel.get('type');
var value = this.model.get(columnName);
var formattedValue = value;
if (columnType === 'geometry') {
if (simpleGeometry === 'point') {
formattedValue = FORMATTED_TYPE_VALUES[columnType](value);
} else {
formattedValue = simpleGeometry && (simpleGeometry.charAt(0).toUpperCase() + simpleGeometry.slice(1));
}
} else if (FORMATTED_TYPE_VALUES[columnType]) {
formattedValue = FORMATTED_TYPE_VALUES[columnType](value);
}
return cellTemplate({
value: value,
formattedValue: formattedValue,
type: columnType,
columnName: columnName,
geometry: simpleGeometry
});
}
});

View File

@@ -0,0 +1,490 @@
var CoreView = require('backbone/core-view');
var $ = require('jquery');
var _ = require('underscore');
var Clipboard = require('clipboard');
var TableBodyRowView = require('./table-body-row-view');
var ContextMenuView = require('builder/components/context-menu/context-menu-view');
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
var addTableRowOperation = require('builder/components/table/operations/table-add-row');
var removeTableRowOperation = require('builder/components/table/operations/table-remove-row');
var editCellOperation = require('builder/components/table/operations/table-edit-cell');
var ConfirmationModalView = require('builder/components/modals/confirmation/modal-confirmation-view');
var TablePaginatorView = require('builder/components/table/paginator/table-paginator-view');
var tableBodyTemplate = require('./table-body.tpl');
var renderLoading = require('builder/components/loading/render-loading');
var ErrorView = require('builder/components/error/error-view');
var tableNoRowsTemplate = require('./table-no-rows.tpl');
var EditorsServiceModel = require('builder/components/table/editors/editors-service-model');
var EditorsModel = require('builder/components/table/editors/types/editor-model');
var errorParser = require('builder/helpers/error-parser');
var magicPositioner = require('builder/helpers/magic-positioner');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var EDITORS_MAP = {
'string': require('builder/components/table/editors/types/editor-string-view'),
'number': require('builder/components/table/editors/types/editor-base-view'),
'boolean': require('builder/components/table/editors/types/editor-boolean-view'),
'date': require('builder/components/table/editors/types/editor-date-view'),
'default': require('builder/components/table/editors/types/editor-string-view')
};
var REQUIRED_OPTS = [
'columnsCollection',
'modals',
'queryGeometryModel',
'querySchemaModel',
'rowsCollection',
'canHideColumns',
'tableViewModel'
];
/*
* Table body view
*/
module.exports = CoreView.extend({
className: 'Table-body',
tagName: 'div',
events: {
'click': '_onClick',
'dblclick': '_onDblClick'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._editors = new EditorsServiceModel();
this._closeEditor = this._closeEditor.bind(this);
this._hideContextMenu = this._hideContextMenu.bind(this);
this._initBinds();
},
render: function () {
this.clearSubViews();
this._destroyScrollBinding();
this.$el.empty();
// Render results when we have the schema and goemetry is not being fetched
if (this._querySchemaModel.isFetched() && !this._queryGeometryModel.isFetching() && !this._rowsCollection.isFetching()) {
if (!this._rowsCollection.size()) {
this._renderNoRows();
} else {
this.$el.html(tableBodyTemplate());
this._rowsCollection.each(this._renderBodyRow, this);
this._initPaginator();
}
} else {
this._renderQueryState();
}
this.$el.toggleClass('Table-body--relative', !!this._tableViewModel.get('relativePositionated'));
return this;
},
_initBinds: function () {
this._queryGeometryModel.bind('change:status', this.render, this);
this._querySchemaModel.bind('change:status', this.render, this);
this._rowsCollection.bind('reset', _.debounce(this.render.bind(this), 20), this);
this._rowsCollection.bind('add', function (model) {
if (this._rowsCollection.size() === 1) {
this.render();
} else {
this._renderBodyRow(model);
}
}, this);
this._rowsCollection.bind('remove', this._onRemoveRow, this);
this._rowsCollection.bind('fail', function (mdl, response) {
if (!response || (response && response.statusText !== 'abort')) {
this._renderError(errorParser(response));
}
}, this);
this.add_related_model(this._queryGeometryModel);
this.add_related_model(this._querySchemaModel);
this.add_related_model(this._rowsCollection);
},
_renderQueryState: function () {
var querySchemaStatus = this._querySchemaModel.get('status');
var nodeReady = this._querySchemaModel.get('ready');
var geometryStatus = this._queryGeometryModel.get('status');
var rowsCollectionStatus = this._rowsCollection.getStatusValue();
if (nodeReady) {
if (querySchemaStatus === 'unavailable' && geometryStatus === 'unavailable' ||
rowsCollectionStatus === 'unavailable') {
this._renderError(this._querySchemaModel.get('query_errors'));
} else {
this._renderLoading();
}
} else {
this._renderLoading();
}
},
_renderLoading: function () {
this.$el.html(
renderLoading({
title: _t('components.table.rows.loading.title')
})
);
},
_renderError: function (desc) {
var view = new ErrorView({
title: _t('components.table.rows.error.title'),
desc: desc || _t('components.table.rows.error.desc')
});
this.addView(view);
this.$el.html(view.render().el);
},
_renderNoRows: function () {
this.$el.html(
tableNoRowsTemplate({
page: this._tableViewModel.get('page'),
customQuery: this._tableViewModel.isCustomQueryApplied()
})
);
},
_initPaginator: function () {
var paginatorView = new TablePaginatorView({
rowsCollection: this._rowsCollection,
tableViewModel: this._tableViewModel,
scrollToBottom: this._scrollToBottom.bind(this)
});
// Bug in Chrome with position:fixed :(, so we have to choose body as
// parent
var $el = $('body');
// But if we have chosen relativePositionated, we should add close
// to the table view
if (this._tableViewModel.get('relativePositionated')) {
$el = this.$el.closest('.Table').parent();
}
$el.append(paginatorView.render().el);
this.addView(paginatorView);
},
_initScrollBinding: function () {
$('.Table').scroll(this._hideContextMenu);
this.$('.js-tbody').scroll(this._hideContextMenu);
},
_destroyScrollBinding: function () {
$('.Table').off('scroll', this._hideContextMenu);
this.$('.js-tbody').off('scroll', this._hideContextMenu);
},
_renderBodyRow: function (mdl) {
var view = new TableBodyRowView({
model: mdl,
columnsCollection: this._columnsCollection,
simpleGeometry: this._queryGeometryModel.get('simple_geom'),
canHideColumns: this._canHideColumns,
tableViewModel: this._tableViewModel
});
this.addView(view);
this.$('.js-tbody').append(view.render().el);
},
_onRemoveRow: function () {
if (!this._rowsCollection.size()) {
this._rowsCollection.resetFetch();
this._queryGeometryModel.resetFetch();
var page = this._tableViewModel.get('page');
if (page > 0) {
this._tableViewModel.set('page', page - 1);
} else {
this.render();
}
}
},
_hasContextMenu: function () {
return this._menuView;
},
_hideContextMenu: function () {
this._unhighlightCell();
this._destroyScrollBinding();
this._menuView.collection.unbind(null, null, this);
this.removeView(this._menuView);
this._menuView.clean();
delete this._menuView;
},
_highlightCell: function ($tableCellItem, $tableRow) {
$tableCellItem.addClass('is-highlighted');
$tableRow.addClass('is-highlighted');
},
_unhighlightCell: function () {
this.$('.Table-cellItem.is-highlighted, .Table-row.is-highlighted').removeClass('is-highlighted');
},
_showContextMenu: function (ev) {
var self = this;
var position = { x: ev.clientX, y: ev.clientY };
var $tableRow = $(ev.target).closest('.Table-row');
var $tableCellItem = $(ev.target).closest('.Table-cellItem');
var modelCID = $tableRow.data('model');
var attribute = $tableCellItem.data('attribute');
var rowModel = self._rowsCollection.get({ cid: modelCID });
var menuItems = [];
menuItems.push({
label: _t('components.table.rows.options.copy'),
val: 'copy',
action: function () {
self._copyValue($tableCellItem);
}
});
if (!this._tableViewModel.isDisabled()) {
menuItems = [
{
label: _t('components.table.rows.options.edit'),
val: 'edit',
action: function () {
self._editCell(rowModel, attribute);
}
}, {
label: _t('components.table.rows.options.create'),
val: 'create',
action: function () {
self._addRow();
}
}
].concat(menuItems);
menuItems.push({
label: _t('components.table.rows.options.delete'),
val: 'delete',
destructive: true,
action: function () {
self._removeRow(rowModel);
}
});
}
// No options?, don't open anything
if (!menuItems.length) {
return false;
}
var collection = new CustomListCollection(menuItems);
this._menuView = new ContextMenuView({
className: 'Table-rowMenu ' + ContextMenuView.prototype.className,
collection: collection,
triggerElementID: modelCID,
position: position
});
this._menuView.$el.css(
magicPositioner({
parentView: $('body'),
posX: position.x,
posY: position.y
})
);
collection.bind('change:selected', function (menuItem) {
var action = menuItem.get('action');
action && action();
}, this);
this._menuView.model.bind('change:visible', function (model, isContextMenuVisible) {
if (this._hasContextMenu() && !isContextMenuVisible) {
this._hideContextMenu();
}
}, this);
this._menuView.show();
this.addView(this._menuView);
this._highlightCell($tableCellItem, $tableRow);
this._initScrollBinding();
},
_onClick: function (ev) {
var isCellOptions = $(ev.target).hasClass('js-cellOptions');
if (isCellOptions) {
if (this._hasContextMenu()) {
this._hideContextMenu();
} else {
this._showContextMenu(ev);
}
}
},
_onDblClick: function (ev) {
var $tableCellItem = $(ev.target).closest('.Table-cellItem');
var isCellOptions = $(ev.target).hasClass('js-cellOptions');
if ($tableCellItem && !isCellOptions) {
var $tableRow = $tableCellItem.closest('.Table-row');
var modelCID = $tableRow.data('model');
var attribute = $tableCellItem.data('attribute');
var rowModel = this._rowsCollection.get({ cid: modelCID });
if (!this._tableViewModel.isDisabled() && rowModel && attribute && attribute !== 'cartodb_id') {
this._editCell(rowModel, attribute);
}
}
},
_copyValue: function ($el) {
// Work-around for Clipboard \o/
this._clipboard = new Clipboard($el.get(0));
$el.click();
this._clipboard.destroy();
},
_addRow: function () {
addTableRowOperation({
rowsCollection: this._rowsCollection
});
},
_initEditorScrollBinding: function () {
$('.Table').scroll(this._closeEditor);
this.$('.js-tbody').scroll(this._closeEditor);
},
_destroyEditorScrollBinding: function () {
$('.Table').unbind('scroll', this._closeEditor);
this.$('.js-tbody').unbind('scroll', this._closeEditor);
},
_closeEditor: function () {
this._unhighlightCell();
this._destroyEditorScrollBinding();
this._editors.unbind(null, null, this);
this._editors.destroy();
},
_saveValue: function (rowModel, attribute, newValue) {
if (rowModel.get(attribute) !== newValue) {
editCellOperation({
rowModel: rowModel,
attribute: attribute,
newValue: newValue
});
}
},
_doCellEdition: function (rowModel, attribute) {
var $tableRow = this.$('[data-model="' + rowModel.cid + '"]');
var $tableCell = $tableRow.find('[data-attribute="' + attribute + '"]');
var $options = $tableCell.find('.js-cellOptions');
var columnModel = _.first(this._columnsCollection.where({ name: attribute }));
var type = columnModel.get('type');
this._highlightCell($tableCell, $tableRow);
this._initEditorScrollBinding();
var model = new EditorsModel({
type: type,
value: rowModel.get(attribute)
});
this._editors.bind('destroyedEditor', this._closeEditor, this);
this._editors.bind('confirmedEditor', function () {
if (model.isValid()) {
this._saveValue(
rowModel,
attribute,
model.get('value')
);
}
}, this);
var position = $options.offset();
if ($tableCell.index() > 1) {
position.right = window.innerWidth - position.left;
delete position.left;
}
if (this._rowsCollection.size() > 4 && ($tableRow.index() + 2) >= (this._rowsCollection.size() - 1)) {
position.bottom = window.innerHeight - position.top;
delete position.top;
} else {
position.top = position.top + 20;
}
var View = EDITORS_MAP[type];
if (!View) {
View = EDITORS_MAP['default'];
}
this._editors.create(
function (editorModel) {
return new View({
editorModel: editorModel,
model: model
});
},
position
);
},
_editCell: function (rowModel, attribute) {
var callback = this._doCellEdition.bind(this, rowModel, attribute);
rowModel.fetchRowIfGeomIsNotLoaded(callback);
},
_removeRow: function (rowModel) {
var self = this;
this._modals.create(
function (modalModel) {
return new ConfirmationModalView({
modalModel: modalModel,
template: require('./modals-templates/remove-table-row.tpl'),
renderOpts: {
cartodb_id: rowModel.get('cartodb_id')
},
loadingTitle: _t('components.table.rows.destroy.loading', {
cartodb_id: rowModel.get('cartodb_id')
}),
runAction: function () {
removeTableRowOperation({
tableViewModel: self._tableViewModel,
rowModel: rowModel,
onSuccess: function () {
modalModel.destroy();
},
onError: function (e) {
modalModel.destroy();
}
});
}
});
}
);
},
_scrollToBottom: function () {
var tbodyHeight = this.$('.js-tbody').get(0).scrollHeight;
this.$('.js-tbody').animate({
scrollTop: tbodyHeight
}, 'slow');
},
clean: function () {
this._destroyScrollBinding();
CoreView.prototype.clean.apply(this);
}
});

View File

@@ -0,0 +1,3 @@
<table>
<tbody class="js-tbody"></tbody>
</table>

View File

@@ -0,0 +1,21 @@
<div class="IntermediateInfo">
<div class="LayoutIcon">
<i class="CDB-IconFont CDB-IconFont-rows"></i>
</div>
<h3 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl">
<% if (page !== 0) { %>
<%- _t('components.table.rows.result.no-page-title', { page: page }) %>
<% } else { %>
<%- _t('components.table.rows.result.no-results-title') %>
<% } %>
</h3>
<p class="CDB-Text CDB-Size-medium u-altTextColor">
<% if (page !== 0) { %>
<%- _t('components.table.rows.result.no-page-desc', { page: page }) %>
<% } else { %>
<%- _t('components.table.rows.result.no-results-desc', {
addRow: _t('components.table.rows.result.no-results-button')
}) %>
<% } %>
</p>
</div>