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,57 @@
var CoreView = require('backbone/core-view');
var randomQuote = require('builder/components/loading/random-quote');
/*
* Content result default view
*/
module.exports = CoreView.extend({
events: {
'click .js-connect': '_onConnectClick'
},
initialize: function (opts) {
if (!opts.routerModel) throw new Error('routerModel is required');
if (!opts.tablesCollection) throw new Error('tablesCollection is required');
if (!opts.userModel) throw new Error('userModel is required');
if (!opts.template) throw new Error('template is required');
this._userModel = opts.userModel;
this._routerModel = opts.routerModel;
this._tablesCollection = opts.tablesCollection;
this.template = opts.template;
this._initBinds();
},
render: function () {
var type = this._routerModel.get('content_type');
this.$el.html(this.template({
page: this._routerModel.get('page'),
tag: this._routerModel.get('tag'),
q: this._routerModel.get('q'),
shared: this._routerModel.get('shared'),
locked: this._routerModel.get('locked'),
library: this._routerModel.get('library'),
quote: randomQuote(),
type: type,
totalItems: this._tablesCollection.size(),
totalEntries: this._tablesCollection.getTotalStat('total_entries')
}));
return this;
},
_initBinds: function () {
this._routerModel.bind('change', this.render, this);
this._tablesCollection.bind('sync', this.render, this);
this.add_related_model(this._routerModel);
this.add_related_model(this._tablesCollection);
},
_onConnectClick: function () {
this.trigger('connectDataset', this);
}
});
@@ -0,0 +1,116 @@
var cdb = require('internal-carto.js');
var CoreView = require('backbone/core-view');
var $ = require('jquery');
var _ = require('underscore');
var moment = require('moment');
var Utils = require('builder/helpers/utils');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var template = require('./dataset-item.tpl');
/**
* View representing an item in the list under datasets route.
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'ModalBlockList-item ModalBlockList-item--full',
events: {
'click .js-tag-link': '_onTagClick',
'click': '_toggleSelected'
},
initialize: function (opts) {
if (!opts.createModel) throw new Error('createModel is required');
if (!opts.userModel) throw new Error('userModel is required');
this._createModel = opts.createModel;
this._userModel = opts.userModel;
this._routerModel = this._createModel.getVisualizationFetchModel();
this.model.on('change', this.render, this);
},
render: function () {
var tableModel = this.model.getTableModel();
var permissionModel = this.model.getPermissionModel();
var synchronizationModel = this.model.getSynchronizationModel();
var tags = this.model.get('tags') || [];
var description = cdb.core.sanitize.html(this.model.get('description'));
var tableGeomColumnTypes = (tableModel.getGeometryType ? tableModel.getGeometryType() : tableModel.geomColumnTypes()) || [];
var d = {
isRaster: this.model.isRaster(),
geometryType: tableGeomColumnTypes.length > 0 ? tableGeomColumnTypes[0] : '',
title: this.model.get('name'),
isOwner: permissionModel.isOwner(this._userModel),
owner: permissionModel.getOwner().renderData(this._userModel),
showPermissionIndicator: !permissionModel.hasWriteAccess(this._userModel),
description: description,
privacy: this.model.get('privacy').toLowerCase(),
timeDiff: moment(this.model.get('updated_at')).fromNow(),
tags: tags,
tagsCount: tags.length,
maxTagsToShow: 3,
rowCount: undefined,
datasetSize: undefined,
syncStatus: undefined,
syncRanAt: undefined
};
var rowCount = tableModel.get('row_count');
if (rowCount >= 0) {
d.rowCount = rowCount;
d.rowCountFormatted = (rowCount < 10000 ? Utils.formatNumber(rowCount) : Utils.readizableNumber(rowCount));
}
var datasetSize = tableModel.get('size');
if (datasetSize >= 0) {
d.datasetSize = Utils.readablizeBytes(datasetSize, true);
}
if (!_.isEmpty(synchronizationModel)) {
d.syncRanAt = moment(synchronizationModel.get('ran_at') || new Date()).fromNow();
d.syncStatus = synchronizationModel.get('state');
}
this.$el.html(template(d));
this._renderTooltips();
// Item selected?
this.$el.toggleClass('is-selected', !!this.model.get('selected'));
return this;
},
_renderTooltips: function () {
if (!_.isEmpty(this.model.get('synchronization'))) {
this.addView(
new TipsyTooltipView({
el: this.$('.DatasetsList-itemStatus'),
title: function (e) {
return $(this).attr('data-title');
}
})
);
}
},
_onTagClick: function (ev) {
var tag = $(ev.target).val();
if (tag) {
this._routerModel.set('tag', tag);
}
},
_toggleSelected: function (ev) {
// Let links use default behaviour
if (ev.target.tagName !== 'A') {
this.killEvent(ev);
if (this._createModel.canSelect(this.model)) {
this.model.set('selected', !this.model.get('selected'));
}
}
}
});
@@ -0,0 +1,73 @@
<div class="DatasetsList-itemCategory is--<%- isRaster ? 'raster' : geometryType %>Dataset">
<% if (syncStatus) { %>
<i
<% if (syncStatus === "failure") { %>
data-title="<%- _t('components.modals.add-layer.datasets.item.sync-failed') %> <%- syncRanAt %>"
<% } else if (syncStatus === "syncing") { %>
data-title="<%- _t('components.modals.add-layer.datasets.item.syncing') %>"
<% } else { %>
data-title="<%- _t('components.modals.add-layer.datasets.item.synced') %> <%- syncRanAt %>"
<% } %>
class="CDB-IconFont CDB-IconFont-wifi DatasetsList-itemStatus is-<%- syncStatus %>"></i>
<% } %>
</div>
<div class="ModalDataset-itemInfo">
<div class="ModalDataset-itemInfoTitle">
<h3 class="CDB-Text CDB-Size-large u-bSpace u-ellipsis">
<%- title %>
<% if (showPermissionIndicator) { %>
<span class="Tag Tag--outline Tag-outline--grey CDB-Text CDB-Size-small u-upperCase">
<%- _t('components.modals.add-layer.datasets.item.read') %>
</span>
<% } %>
</h3>
<% if (description && description.length > 0) { %>
<p class="u-ellipsis CDB-Text CDB-Size-medium u-altTextColor" title="<%- description %>"><%- description %></p>
<% } else { %>
<span class="NoResults CDB-Text CDB-Size-medium"><%- _t('components.modals.add-layer.datasets.item.no-description') %></span>
<% } %>
</div>
<div>
<div class="DatasetsList-itemMeta">
<span class="CDB-Tag is-<%- privacy %> CDB-Text is-semibold CDB-Size-small u-upperCase">
<%- privacy %>
</span>
<% if (rowCount) { %>
<span class="RowsIndicator">
<span class="CDB-Text CDB-Size-small u-altTextColor"><%- rowCountFormatted %> <%- _t('components.modals.add-layer.datasets.item.rows-pluralize', { smart_count: rowCount }) %></span>
</span>
<% } %>
<% if (datasetSize) { %>
<span class="SizeIndicator">
<span class="CDB-Text CDB-Size-small u-altTextColor"><%- datasetSize %></span>
</span>
<% } %>
<span class="DatasetsList-itemTimeDiff DefaultTimeDiff">
<span class="CDB-Text CDB-Size-small u-altTextColor"><%- timeDiff %></span>
<% if (!isOwner) { %>
<span class="CDB-Text CDB-Size-small u-altTextColor u-lSpace--xl u-rSpace">
<%- _t('components.modals.add-layer.datasets.item.by') %>
</span>
<span class="DatasetsList-avatar">
<img class="DatasetsList-avatarImg" src="<%- owner.avatar_url %>" alt="<%- owner.name || owner.username %>" title="<%- owner.name || owner.username %>" />
</span>
<% } %>
</span>
</div>
<div class="DatasetsList-itemMeta DatasetsList-itemTags">
<% if (tagsCount > 0) { %>
<div class="DefaultTags CDB-Text CDB-Size-small">
<% for (var i = 0, l = Math.min(maxTagsToShow, tags.length); i < l; ++i) { %>
<button class="CDB-Text CDB-Size-small u-upperCase DefaultTags-item js-tag-link u-actionTextColor" value="<%- tags[i] %>"><%- tags[i] %></button><% if (i !== (l-1)) { %><% } %>
<% } %>
<% if (tagsCount > maxTagsToShow) { %>
<%- _t('components.modals.add-layer.datasets.item.tags-more', { tagsCount: tagsCount - maxTagsToShow }) %>
<% } %>
</div>
<% } else { %>
<span class="NoResults CDB-Text CDB-Size-small u-altTextColor"><%- _t('components.modals.add-layer.datasets.item.no-tags') %></span>
<% } %>
</div>
</div>
</div>
@@ -0,0 +1,9 @@
<div class="IntermediateInfo">
<div class="LayoutIcon LayoutIcon--negative">
<i class="CDB-IconFont CDB-IconFont-cockroach"></i>
</div>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
<%- _t('components.modals.add-layer.datasets.error.title') %>
</h4>
<p class="CDB-Text CDB-Size-medium u-altTextColor"><%- _t('components.modals.add-layer.datasets.error.desc') %> <a class="js-mail-link" href="mailto:support@carto.com">support@carto.com</a>.</p>
</div>
@@ -0,0 +1,58 @@
var CoreView = require('backbone/core-view');
var DATASETS_ITEMS = {
'datasets': require('./dataset-item-view'),
'remotes': require('./remote-dataset-item-view')
};
/**
* View representing the list of datasets
*/
module.exports = CoreView.extend({
tagName: 'ul',
className: 'DatasetsList fs-DatasetsList',
initialize: function (opts) {
if (!opts.createModel) throw new Error('createModel is required');
if (!opts.userModel) throw new Error('userModel is required');
this._createModel = opts.createModel;
this._userModel = opts.userModel;
this._tablesCollection = this._createModel.getTablesCollection();
this._initBinds();
},
render: function () {
this.clearSubViews();
this._tablesCollection.each(this._addItem, this);
return this;
},
_initBinds: function () {
this._tablesCollection.bind('sync', this.render, this);
this.add_related_model(this._tablesCollection);
},
_addItem: function (m, i) {
var type = m.get('type') === 'remote' ? 'remotes' : 'datasets';
var item = new DATASETS_ITEMS[type]({
model: m,
createModel: this._createModel,
userModel: this._userModel
});
this.addView(item);
this.$el.append(item.render().el);
},
show: function () {
this.$el.removeClass('is-hidden');
},
hide: function () {
this.$el.addClass('is-hidden');
}
});
@@ -0,0 +1,11 @@
<div class="IntermediateInfo">
<div class="CDB-LoaderIcon CDB-LoaderIcon--big is-dark js-loader">
<svg class="CDB-LoaderIcon-spinner" viewbox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"/>
</svg>
</div>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-bSpace u-secondaryTextColor u-tSpace-xl">
<%- _t('components.modals.add-layer.datasets.' + (q || tag ? 'searching' : 'loading')) %>...
</h4>
<div class="CDB-Text CDB-Size-medium u-altTextColor"><%= quote %></div>
</div>
@@ -0,0 +1,27 @@
<div class="IntermediateInfo">
<div class="LayoutIcon <% (q || tag) ? 'LayoutIcon--negative' : '' %>">
<i class="CDB-IconFont
<% if (shared) { %> CDB-IconFont-defaultUser
<% } else if (locked) { %> CDB-IconFont-lock
<% } else if (library) { %> CDB-IconFont-dribbble
<% } else { %> CDB-IconFont-lens <% } %>" />
</div>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-bSpace u-secondaryTextColor u-tSpace-xl">
<% if (page > 1 || totalItems === 0 && totalEntries > 0) { %>
<%- _t('components.modals.add-layer.datasets.no-results.desc') %>
<% } %>
<% if (( tag || q ) && totalItems === 0 && totalEntries === 0) { %>
0 <%- tag || q %> <%- type %> <%- _t('components.modals.add-layer.datasets.no-results.found') %>
<% } %>
<% if (page === 1 && !tag && !q && totalItems === 0 && totalEntries === 0) { %>
<%- _t('components.modals.add-layer.datasets.no-results.there-are-no') %> <%- shared === "only" ? 'shared' : '' %> <%- locked ? 'locked' : '' %> <%- library ? 'library' : '' %> <%- type %>
<% } %>
</h4>
<p class="CDB-Text CDB-Size-medium u-altTextColor">
<% if (!tag && !q && totalItems === 0 && totalEntries === 0) { %>
<%- _t('components.modals.add-layer.datasets.no-results.no-fun', { type: type }) %>
<% } %>
</p>
</div>
@@ -0,0 +1,72 @@
var CoreView = require('backbone/core-view');
var PaginationModel = require('builder/components/pagination/pagination-model');
var PaginationView = require('builder/components/pagination/pagination-view');
/**
* Responsible for the datasets paginator
* ___________________________________________________________________________
* | |
* | Page 2 of 42 [1] 2 [3][4][5] |
* |___________________________________________________________________________|
*
*/
module.exports = CoreView.extend({
className: 'DatasetsPaginator',
initialize: function (opts) {
if (!opts.routerModel) throw new TypeError('routerModel is required');
if (!opts.tablesCollection) throw new TypeError('tablesCollection is required');
this._routerModel = opts.routerModel;
this._tablesCollection = opts.tablesCollection;
this.model = new PaginationModel({
current_page: this._routerModel.get('page')
});
this._initBinds();
this._initViews();
},
render: function () {
this.clearSubViews();
this.$el.html('');
if (this.model.shouldBeVisible()) {
this.$el.append(this.paginationView.render().el);
}
return this;
},
_initBinds: function () {
this.model.bind('change', this.render, this);
this.model.bind('change:current_page', function () {
this._routerModel.set('page', this.model.get('current_page'));
}, this);
this._tablesCollection.bind('sync', this._updatePaginationModelByCollection, this);
this._routerModel.bind('change:page', this._updatePaginationModelByRouterModel, this);
this.add_related_model(this._routerModel);
this.add_related_model(this._tablesCollection);
this.add_related_model(this.model);
},
_initViews: function () {
this.paginationView = new PaginationView({
model: this.model
});
this.addView(this.paginationView);
},
_updatePaginationModelByCollection: function () {
this.model.set({
per_page: this._tablesCollection.getDefaultParam('per_page'),
total_count: this._tablesCollection.getTotalStat('total_entries')
});
},
_updatePaginationModelByRouterModel: function () {
this.model.set('current_page', this._routerModel.get('page'));
}
});
@@ -0,0 +1,218 @@
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var ContentResultView = require('./content-result-view');
var DatasetsListView = require('./datasets-list-view');
var DatasetsPaginationView = require('./datasets-pagination-view');
var noResultsTemplate = require('./no-datasets.tpl');
var datasetsNoResultTemplate = require('./datasets-no-result.tpl');
var datasetsErrorTemplate = require('./datasets-error.tpl');
var datasetsLoaderTemplate = require('./datasets-loader.tpl');
/**
* Datasets list view
*
* Show datasets view to select them for
* creating a map or importing a dataset
*
*/
module.exports = CoreView.extend({
initialize: function (opts) {
if (!opts.createModel) throw new Error('createModel is required');
if (!opts.userModel) throw new Error('userModel is required');
this._createModel = opts.createModel;
this._userModel = opts.userModel;
this._routerModel = this._createModel.getVisualizationFetchModel();
this._tablesCollection = this._createModel.getTablesCollection();
this._initViews();
this._initBinds();
this._onDataLoading();
},
_initBinds: function () {
this._routerModel.bind('change', this._onRouterChange, this);
this._tablesCollection.bind('loading', this._onDataLoading, this);
this._tablesCollection.bind('sync', this._onDataFetched, this);
this._tablesCollection.bind('error', function (e) {
// Old requests can be stopped, so aborted requests are not
// considered as an error
if (!e || (e && e.statusText !== 'abort')) {
this._onDataError();
}
}, this);
this.add_related_model(this._routerModel);
this.add_related_model(this._tablesCollection);
},
_initViews: function () {
this.controlledViews = {}; // All available views
this.enabledViews = []; // Visible views
var noDatasetsView = new ContentResultView({
className: 'ContentResult no-datasets',
userModel: this._userModel,
routerModel: this._routerModel,
tablesCollection: this._tablesCollection,
template: noResultsTemplate
});
noDatasetsView.bind('connectDataset', function () {
if (this._userModel.canCreateDatasets()) {
this._createModel.set('listing', 'import');
}
}, this);
noDatasetsView.render().hide();
this.controlledViews['no_datasets'] = noDatasetsView;
this.$el.append(noDatasetsView.el);
this.addView(noDatasetsView);
var listView = new DatasetsListView({
userModel: this._userModel,
createModel: this._createModel
});
this.controlledViews.list = listView;
this.$el.append(listView.render().el);
this.addView(listView);
var noResultsView = new ContentResultView({
userModel: this._userModel,
routerModel: this._routerModel,
tablesCollection: this._tablesCollection,
template: datasetsNoResultTemplate
});
noResultsView.render().hide();
this.controlledViews.no_results = noResultsView;
this.$el.append(noResultsView.el);
this.addView(noResultsView);
var errorView = new ContentResultView({
userModel: this._userModel,
routerModel: this._routerModel,
tablesCollection: this._tablesCollection,
template: datasetsErrorTemplate
});
errorView.render().hide();
this.controlledViews.error = errorView;
this.$el.append(errorView.el);
this.addView(errorView);
var mainLoaderView = new ContentResultView({
userModel: this._userModel,
routerModel: this._routerModel,
tablesCollection: this._tablesCollection,
template: datasetsLoaderTemplate
});
this.controlledViews.main_loader = mainLoaderView;
this.$el.append(mainLoaderView.render().el);
this.addView(mainLoaderView);
var datasetsPaginationView = new DatasetsPaginationView({
routerModel: this._routerModel,
tablesCollection: this._tablesCollection
});
this.controlledViews.content_footer = datasetsPaginationView;
this.$el.append(datasetsPaginationView.render().el);
this.addView(datasetsPaginationView);
},
_onRouterChange: function () {
this._hideBlocks();
this._showBlocks([ 'main_loader' ]);
},
/**
* Arguments may vary, depending on if it's the collection or a model that triggers the event callback.
* @private
*/
_onDataFetched: function () {
var activeViews = [ 'content_footer' ];
var tag = this._routerModel.get('tag');
var q = this._routerModel.get('q');
var shared = this._routerModel.get('shared');
var locked = this._routerModel.get('locked');
var library = this._routerModel.get('library');
if (library && this._tablesCollection.getTotalStat('total_user_entries') === 0) {
activeViews.push('no_datasets');
}
if (this._tablesCollection.size() === 0) {
if (!tag && !q && shared === 'no' && !locked) {
if (!library) {
this._goToLibrary();
return;
} else {
activeViews.push('no_results');
}
} else {
activeViews.push('no_results');
}
} else {
activeViews.push('list');
}
this._hideBlocks();
this._showBlocks(activeViews);
},
_onDataLoading: function () {
this._hideBlocks();
this._showBlocks([ 'main_loader' ]);
},
_onDataError: function (e) {
this._hideBlocks();
this._showBlocks([ 'error' ]);
},
_showBlocks: function (views) {
var self = this;
if (views) {
_.each(views, function (v) {
if (self.controlledViews[v]) {
self.controlledViews[v].show();
self.enabledViews.push(v);
}
});
} else {
self.enabledViews = [];
_.each(this.controlledViews, function (v) {
v.show();
self.enabledViews.push(v);
});
}
},
_goToLibrary: function () {
this._routerModel.set({
shared: 'no',
library: true,
page: 1
});
},
_hideBlocks: function (views) {
var self = this;
if (views) {
_.each(views, function (v) {
if (self.controlledViews[v]) {
self.controlledViews[v].hide();
self.enabledViews = _.without(self.enabledViews, v);
}
});
} else {
_.each(this.controlledViews, function (v) {
v.hide();
});
self.enabledViews = [];
}
},
_isBlockEnabled: function (name) {
if (name) {
return _.contains(this.enabledViews, name);
}
return false;
}
});
@@ -0,0 +1,12 @@
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl">
<%- _t('components.modals.add-layer.datasets.no-datasets.title') %>
</h4>
<p class="CDB-Text CDB-Size-medium u-altTextColor">
<% var connectDatasetHTML = '<button class="Button--link js-connect">' + _t('components.modals.add-layer.datasets.no-datasets.connect-datasets') + '</button>'; %>
<% var searchHTML = '<strong>' + _t('components.modals.add-layer.datasets.no-datasets.search') + '</strong>'; %>
<%= _t('components.modals.add-layer.datasets.no-datasets.desc', {
connectDataset: connectDatasetHTML,
search: searchHTML
}) %>
</p>
<div class="NoDatasets-illustration"></div>
@@ -0,0 +1,123 @@
var cdb = require('internal-carto.js');
var $ = require('jquery');
var DatasetItem = require('./dataset-item-view');
var moment = require('moment');
var markdown = require('markdown');
var Utils = require('builder/helpers/utils');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var UploadConfig = require('builder/config/upload-config');
var template = require('./remote-dataset-item.tpl');
/**
* Remote dataset item view
*
*/
module.exports = DatasetItem.extend({
tagName: 'li',
className: 'ModalBlockList-item ModalBlockList-item--full',
events: {
'click .js-tag-link': '_onTagClick',
'click': '_toggleSelected'
},
render: function () {
var tableModel = this.model.getTableModel();
var tags = this.model.get('tags') || [];
var description = cdb.core.sanitize.html(this.model.get('description') || '');
var source = markdown.toHTML(this.model.get('source') || '');
var tableGeomColumnTypes = tableModel.getGeometryType() || [];
var d = {
isRaster: this.model.isRaster(),
geometryType: tableGeomColumnTypes.length > 0 ? tableGeomColumnTypes[0] : '',
title: this.model.get('display_name') || this.model.get('name'),
source: source,
description: description,
timeDiff: moment(this.model.get('updated_at')).fromNow(),
tags: tags,
tagsCount: tags.length,
routerModel: this._routerModel,
maxTagsToShow: 3,
canImportDataset: this._canImportDataset(),
rowCount: undefined,
datasetSize: undefined
};
var rowCount = tableModel.get('row_count');
if (rowCount >= 0) {
d.rowCount = rowCount;
d.rowCountFormatted = (rowCount < 10000 ? Utils.formatNumber(rowCount) : Utils.readizableNumber(rowCount));
}
var datasetSize = tableModel.get('size');
if (datasetSize >= 0) {
d.datasetSize = Utils.readablizeBytes(
datasetSize,
datasetSize.toString().length > 9
);
}
this.$el.html(template(d));
this._setItemClasses();
this._renderTooltips();
return this;
},
_setItemClasses: function () {
// Item selected?
this.$el.toggleClass('is-selected', !!this.model.get('selected'));
// Check if it is selectable
this.$el.toggleClass('is-selectable', !!this._canImportDataset());
// Check if it is importable
this.$el.toggleClass('is-banned', !this._canImportDataset());
},
_renderTooltips: function () {
this.addView(
new TipsyTooltipView({
el: this.$('.DatasetsList-itemStatus'),
title: function (e) {
return $(this).attr('data-title');
}
})
);
},
_onTagClick: function (ev) {
if (ev) {
this.killEvent(ev);
}
var tag = $(ev.target).val();
if (tag) {
this._routerModel.set({
tag: tag,
library: true
});
}
},
_canImportDataset: function () {
var tableModel = this.model.getTableModel();
var tableSize = tableModel.get('size') || 0;
return (
this._userModel.get('remaining_byte_quota') * UploadConfig.fileTimesBigger >= tableSize &&
this._userModel.get('limits')['import_file_size'] > tableSize
);
},
_toggleSelected: function (ev) {
// Let links use default behaviour
if (ev.target.tagName !== 'A') {
this.killEvent(ev);
if (this._canImportDataset() && this._createModel.canSelect(this.model)) {
this.model.set('selected', !this.model.get('selected'));
}
}
}
});
@@ -0,0 +1,44 @@
<div class="DatasetsList-itemCategory is--<%- isRaster ? 'raster' : geometryType %>Dataset">
<i data-title="Public dataset" class="CDB-IconFont CDB-IconFont-book DatasetsList-itemStatus <%- canImportDataset ? 'is-public' : 'is-banned' %>"></i>
</div>
<div class="ModalDataset-itemInfo">
<div class="ModalDataset-itemInfoTitle">
<h3 class="CDB-Text CDB-Size-large u-bSpace u-ellipsis"><%- title %></h3>
<% if (description && description.length > 0) { %>
<p class="u-ellipsis CDB-Text CDB-Size-medium u-altTextColor" title="<%- description %>"><%- description %></p>
<% } else { %>
<span class="NoResults CDB-Text CDB-Size-medium"><%- _t('components.modals.add-layer.datasets.item.no-description')%></span>
<% } %>
</div>
<div>
<ul class="DatasetsList-itemMeta CDB-Text CDB-Size-small u-altTextColor">
<% if (rowCount) { %>
<li class="RowsIndicator">
<%- rowCountFormatted %> <%- _t('components.modals.add-layer.datasets.item.rows-pluralize', { smart_count: rowCount }) %>
</li>
<% } %>
<% if (datasetSize) { %>
<li class="SizeIndicator">
<%- datasetSize %>
</li>
<% } %>
<li class="DatasetsList-itemTimeDiff DefaultTimeDiff CDB-Text CDB-Size-small u-altTextColor">
<%- timeDiff %> <span class="DatasetsList-itemSource js-source"><% if (source) { %><%- _t('components.modals.add-layer.datasets.item.from') %> <%= cdb.core.sanitize.html(source) %><% } %></span>
</li>
</ul>
<div class="DatasetsList-itemMeta DatasetsList-itemTags">
<% if (tagsCount > 0) { %>
<div class="DefaultTags CDB-Text CDB-Size-small">
<% for (var i = 0, l = Math.min(maxTagsToShow, tags.length); i < l; ++i) { %>
<button class="CDB-Text CDB-Size-small u-upperCase DefaultTags-item js-tag-link u-actionTextColor" value="<%- tags[i] %>"><%- tags[i] %></button><% if (i !== (l-1)) { %><% } %>
<% } %>
<% if (tagsCount > maxTagsToShow) { %>
<%- _t('components.modals.add-layer.datasets.item.tags-more', { tagsCount: tagsCount - maxTagsToShow }) %>
<% } %>
</div>
<% } else { %>
<span class="NoResults CDB-Text CDB-Size-small u-altTextColor"><%- _t('components.modals.add-layer.datasets.item.no-tags') %></span>
<% } %>
</div>
</div>
</div>