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,41 @@
const CoreView = require('backbone/core-view');
const randomQuote = require('builder/components/loading/random-quote');
const checkAndBuildOpts = require('builder/helpers/required-opts');
const REQUIRED_OPTS = [
'collection',
'model',
'template'
];
/*
* Content result default view
*/
module.exports = CoreView.extend({
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._initBinds();
},
render: function () {
this.$el.html(this._template({
defaultUrl: '',
page: this._collection.options.get('page'),
isSearching: this._model.get('is_searching'),
tag: this._collection.options.get('tags'),
q: this._collection.options.get('q'),
quote: randomQuote(),
type: this._collection.options.get('type'),
totalItems: this._collection.size(),
totalEntries: this._collection.total_entries,
msg: ''
}));
return this;
},
_initBinds: function () {
this.listenTo(this._collection, 'change remove add reset', this.render);
}
});
@@ -0,0 +1,10 @@
<div class="IntermediateInfo">
<div class="LayoutIcon LayoutIcon--negative">
<i class="CDB-IconFont CDB-IconFont-cockroach"></i>
</div>
<% if (msg) { %>
<p class="CDB-Text CDB-Size-medium u-altTextColor u-tSpace-xl"><%= msg %></p>
<% } %>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl">Oouch! There has been an error</h4>
<p class="CDB-Text CDB-Size-medium u-altTextColor">If the problem persists contact us at <a class="js-mail-link" href="mailto:support@carto.com">support@carto.com</a>.</p>
</div>
@@ -0,0 +1,80 @@
const _ = require('underscore');
const moment = require('moment');
const CoreView = require('backbone/core-view');
const Utils = require('builder/helpers/utils');
const checkAndBuildOpts = require('builder/helpers/required-opts');
const MapCardPreview = require('dashboard/components/mapcard-preview-view');
const template = require('./dataset-item.tpl');
const GEOM_TYPES = ['point', 'polygon', 'line', 'raster'];
const REQUIRED_OPTS = [
'configModel'
];
/**
* View representing an item in the list under datasets route.
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'MapsList-item',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
},
render: function () {
this.clearSubViews();
const vis = this.model;
const date = vis.get('order') === 'updated_at' ? vis.get('updated_at') : vis.get('created_at');
this.$el.html(template({
vis: vis.attributes,
datasetSize: this._getDatasetSize(vis.get('table')['size']),
geomType: this._getGeometryType(vis.get('table')['geometry_types']),
account_host: this._configModel.get('account_host'),
dataset_base_url: this._configModel.get('dataset_base_url'),
dateFromNow: moment(date).fromNow()
}));
this._renderMapThumbnail();
return this;
},
_renderMapThumbnail: function () {
const username = this.model.get('permission')['owner']['username'];
const mapCardPreview = new MapCardPreview({
el: this.$('.js-header'),
username: username,
width: 298,
height: 220,
visId: this.model.get('id'),
mapsApiResource: this._configModel.getMapsResourceName(username),
config: this._configModel
});
if (this.imageURL) {
mapCardPreview.loadURL(this.imageURL);
} else {
mapCardPreview.load();
}
mapCardPreview.bind('loaded', function (url) {
this.imageURL = url;
}, this);
this.addView(mapCardPreview);
},
_getGeometryType: function (geomTypes) {
const geomType = (geomTypes && geomTypes[0] || '').toLowerCase();
return _.find(GEOM_TYPES, type => geomType.indexOf(type) !== -1);
},
_getDatasetSize: function (size) {
return size ? Utils.readablizeBytes(size, true).split(' ') : 0;
}
});
@@ -0,0 +1,41 @@
<div class="MapCard MapCard--borderless MapCard--squared js-card" data-vis-id="<%- vis.id %>" data-username="<%- vis.permission.owner.username %>">
<a href="<%- dataset_base_url %><%- vis.name %>" target="_blank" class="MapCard-header js-header">
<div class="MapCard-loader js-loader"></div>
</a>
<div class="MapCard-content">
<div class="MapCard-contentFooter MapCard-contentFooter--with-icon">
<div class="MapCard-contentFooterIcon">
<div class="DatasetsList-itemCategory is--<%= geomType %>Dataset"></div>
</div>
<div class="MapCard-contentFooterDetails u-ellipsLongText">
<div class="MapCard-contentFooterTitle">
<h3 class="MapCard-title DefaultTitle CDB-Text is-semibold CDB-Size-large">
<a href="<%- dataset_base_url %><%- vis.name %>" target="_blank" class="DefaultTitle-link u-ellipsLongText" title="<%- vis.display_name %>">
<% if (vis.display_name) { %>
<%- vis.display_name %>
<% } else { %>
<%- vis.name %>
<% } %>
</a>
</h3>
</div>
<div class="MapCard-contentFooterIcons CDB-Size-medium u-altTextColor">
<div class="MapCard-contentFooterDetails--left MapCard-contentFooterDetails--noright">
<div class="MapCard-contentFooterTimeDiff DefaultTimeDiff">
<i class="CDB-IconFont CDB-IconFont-clock DefaultTimeDiff-icon"></i>
<%- dateFromNow %>
</div>
<% if (datasetSize && datasetSize[0] > 0) { %>
<div class="MapCard-contentFooterIcon u-hideOnMobile">
<i class="CDB-IconFont CDB-IconFont-floppy SizeIndicator-icon"></i>
<span class="MapCardIcon-counter"><%- datasetSize[0] %></span> <span class="MapCardIcon-label"><%- datasetSize[1] %></span>
</div>
<% } %>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,82 @@
const CoreView = require('backbone/core-view');
const _ = require('underscore');
const DatasetsItemView = require('./dataset-item-view');
const PlaceholderItem = require('./placeholder-item-view');
const checkAndBuildOpts = require('builder/helpers/required-opts');
const REQUIRED_OPTS = [
'configModel',
'collection'
];
const MAP_CARDS_PER_ROW = 3;
/**
* View representing the list of items
*/
module.exports = CoreView.extend({
tagName: 'ul',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._initBinds();
},
render: function () {
if (this._collection.options.get('page') === 1) {
this.clearSubViews();
}
this._collection.each(this._addItem, this);
let className = 'MapsList';
if (this._collection._ITEMS_PER_PAGE * this._collection.options.get('page') >= this._collection.total_entries) {
className += ' is-bottom';
}
this.$el.attr('class', className);
if (this._collection.size() > 0) {
this._fillEmptySlotsWithPlaceholderItems();
}
return this;
},
_initBinds: function () {
this.listenTo(this._collection, 'reset loaded', this.render);
},
show: function () {
this.$el.removeClass('is-hidden');
},
hide: function () {
this.$el.addClass('is-hidden');
},
_addItem: function (model) {
const itemView = new DatasetsItemView({
model,
configModel: this._configModel
});
this.addView(itemView);
this.$el.append(itemView.render().el);
},
_fillEmptySlotsWithPlaceholderItems: function () {
_.times(this._emptySlotsCount(), function () {
var view = new PlaceholderItem();
this.$el.append(view.render().el);
this.addView(view);
}, this);
},
_emptySlotsCount: function () {
return (this._collection._ITEMS_PER_PAGE - this._collection.size()) % MAP_CARDS_PER_ROW;
}
});
@@ -0,0 +1,4 @@
<div class="MapCard MapCard--squared MapCard--borderless">
<div class="MapCard-header MapCard-header--fake"></div>
<div class="MapCard-content"></div>
</div>
@@ -0,0 +1,20 @@
const CoreView = require('backbone/core-view');
const template = require('./placeholder-item-template.tpl');
/**
* Represents a map card on data library.
*/
module.exports = CoreView.extend({
className: 'MapsList-item MapsList-item--fake',
tagName: 'li',
render: function () {
this.clearSubViews();
this.$el.html(template());
return this;
}
});
@@ -0,0 +1,5 @@
<div class="IntermediateInfo">
<div class="Spinner"></div>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m"><%- q || tag ? 'Searching' : 'Loading' %>...</h4>
<div class="CDB-Text CDB-Size-medium u-altTextColor"><%= quote %></div>
</div>
@@ -0,0 +1,3 @@
<div class="u-inner u-txt-center">
<button class="Button Button--gray Button--centered is-hidden js-more DataLibrary-more">View more</button>
</div>
@@ -0,0 +1,11 @@
<div class="IntermediateInfo">
<div class="LayoutIcon">
<i class="CDB-IconFont CDB-IconFont-lens" />
</div>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m">
Oh! No results
</h4>
<p class="CDB-Text CDB-Size-medium u-altTextColor">
Your search was correct but returned no results, please try with a different set of parameters before running it again
</p>
</div>
@@ -0,0 +1,11 @@
<div class="IntermediateInfo">
<div class="LayoutIcon LayoutIcon--negative">
<i class="CDB-IconFont CDB-IconFont-info"></i>
</div>
<% if (msg) { %>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl"><%= msg %></h4>
<% } else { %>
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl">Oouch! There has been an error</h4>
<% } %>
<p class="CDB-Text CDB-Size-medium u-altTextColor">If the problem persists contact us at <a class="js-mail-link" href="mailto:support@carto.com">support@carto.com</a>.</p>
</div>
@@ -0,0 +1,271 @@
const $ = require('jquery');
const _ = require('underscore');
const Backbone = require('backbone');
const CoreView = require('backbone/core-view');
const FiltersView = require('dashboard/views/data-library/filters/filters-view');
const ListView = require('dashboard/views/data-library/content/list/list-view');
const ContentView = require('dashboard/views/data-library/content/content-view');
const DatasetsCollection = require('dashboard/data/datasets-collection');
const DataLibraryHeaderView = require('dashboard/views/data-library/header/header-view');
const moreTemplate = require('dashboard/views/data-library/content/more-template.tpl');
const noResultsTemplate = require('dashboard/views/data-library/content/no-results-template.tpl');
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
const loaderTemplate = require('dashboard/views/data-library/content/loader-template.tpl');
const checkAndBuildOpts = require('builder/helpers/required-opts');
const REQUIRED_OPTS = [
'configModel'
];
module.exports = CoreView.extend({
events: {
'click .js-more': '_onClickMore'
},
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
this._initModels();
this._initViews();
this._initBinds();
},
render: function () {
this._fetchCollection();
return this;
},
_initModels: function () {
this.model = new Backbone.Model({
vis_count: 0,
show_countries: false,
is_searching: false
});
this.collection = new DatasetsCollection(null, { configModel: this._configModel });
this._resetOptions();
},
_initViews: function () {
this.controlledViews = {}; // All available views
this.enabledViews = []; // Visible views
const dataLibraryHeader = new DataLibraryHeaderView({
model: this.model,
collection: this.collection,
configModel: this._configModel
});
$('.js-Header--datalibrary').append(dataLibraryHeader.render().el);
this.addView(dataLibraryHeader);
dataLibraryHeader.load();
const filtersView = new FiltersView({
collection: this.collection,
model: this.model
});
$('.Filters').append(filtersView.render().el);
this.addView(filtersView);
const moreView = new ContentView({
model: this.model,
collection: this.collection,
template: moreTemplate
});
this.$el.append(moreView.render().el);
this.addView(moreView);
const listView = new ListView({
collection: this.collection,
configModel: this._configModel
});
$('.js-DataLibrary-content').append(listView.render().el);
this.addView(listView);
this.controlledViews['list'] = listView;
const noResultsView = new ContentView({
model: this.model,
collection: this.collection,
template: noResultsTemplate
});
this.$el.append(noResultsView.render().el);
this.addView(noResultsView);
this.controlledViews['no_results'] = noResultsView;
const errorView = new ContentView({
model: this.model,
collection: this.collection,
template: errorTemplate
});
this.$el.append(errorView.render().el);
this.addView(errorView);
this.controlledViews['error'] = errorView;
const mainLoaderView = new ContentView({
model: this.model,
collection: this.collection,
template: loaderTemplate
});
this.$el.append(mainLoaderView.render().el);
this.addView(mainLoaderView);
this.controlledViews['main_loader'] = mainLoaderView;
},
_fetchCollection: function () {
this.collection.fetch();
},
_initBinds: function () {
this.model.bind('change:show_more', this._onChangeShowMore, this);
this.model.bind('change:vis_count', this._onChangeVisCount, this);
this.listenTo(this.collection.options, 'change:tags', this._resetVisCount);
this.listenTo(this.collection.options, 'change:bbox', this._resetVisCount);
this.listenTo(this.collection.options, 'change', this._onCollectionOptionsChange);
this.listenTo(this.collection, 'reset loaded', this._onCollectionReset);
this.listenTo(this.collection, 'loading', this._onCollectionLoading);
this.listenTo(this.collection, 'error', this._onCollectionError);
},
_resetVisCount: function () {
this.model.set({ vis_count: 0 });
},
_onCollectionOptionsChange: function () {
this.model.set({ show_more: false });
this._fetchCollection();
},
_onCollectionError: function (collection, event, opts) {
if (!event || (event && event.statusText !== 'abort')) {
this._onDataError(event);
}
},
_onCollectionLoading: function () {
if (this.collection.options.get('page') === 1) {
this._showLoader();
} else {
this._showLoaderOnly();
}
},
_onCollectionReset: function () {
this._onDataFetched();
},
_onChangeVisCount: function () {
if (this.model.get('vis_count') >= this.collection.total_entries) {
this.model.set({ show_more: false });
} else {
this.model.set({ show_more: true });
}
},
_onChangeShowMore: function () {
this.$('.js-more').toggleClass('is-hidden', !this.model.get('show_more'));
},
_onDataFetched: function () {
const activeViews = [];
if (this.collection.size() === 0) {
activeViews.push('no_results');
} else {
this.model.set({
vis_count: this.model.get('vis_count') + this.collection.length,
show_more: true
});
activeViews.push('list');
}
this._hideBlocks();
this._showBlocks(activeViews);
},
_onDataError: function (error) {
if (window.trackJs && window.trackJs.track) {
window.trackJs.track(error);
}
this._hideBlocks();
this._showBlocks(['error']);
},
_showBlocks: function (views) {
if (views) {
_.each(views, (view) => {
this.controlledViews[view].show();
this.enabledViews.push(view);
});
} else {
this.enabledViews = [];
_.each(this.controlledViews, (view) => {
view.show();
this.enabledViews.push(view);
});
}
},
_hideBlocks: function (views) {
if (views) {
_.each(views, (view) => {
this.controlledViews[view].hide();
this.enabledViews = _.without(this.enabledViews, view);
});
} else {
_.each(this.controlledViews, (view) => {
view.hide();
});
this.enabledViews = [];
}
},
_isBlockEnabled: function (name) {
if (name) {
return _.contains(this.enabledViews, name);
}
return false;
},
_showLoader: function () {
this._hideBlocks();
this._showBlocks(['main_loader']);
},
_showLoaderOnly: function () {
this._showBlocks(['main_loader']);
},
_hideLoader: function () {
this._hideBlocks(['main_loader']);
},
_resetOptions: function () {
this.collection.options.set({
q: '',
order: 'updated_at',
page: 1,
tags: '',
bbox: '',
source: [],
type: 'table'
});
},
_onClickMore: function (event) {
this.killEvent(event);
this.model.set({ show_more: false });
this.collection.options.set({
page: this.collection.options.get('page') + 1
});
}
});
@@ -0,0 +1,49 @@
const $ = require('jquery');
const DropdownAdminView = require('dashboard/components/dropdown/dropdown-admin-view');
const template = require('./dropdown.tpl');
/**
* The content of the dropdown menu opened by the user in the data-library filters, e.g.:
* Category ▼
* ______/\____
* | |
* | this |
* |____________|
*/
module.exports = DropdownAdminView.extend({
className: 'CDB-Text Dropdown Dropdown--public',
events: {
'click .js-all': '_onClickAll',
'click .js-categoryLink': '_onClickLink'
},
render: function () {
this.$el.html(template());
// TODO: taken from existing code, how should dropdowns really be added to the DOM?
$('body').append(this.el);
return this;
},
_onClickAll: function (event) {
this.collection.options.set({
tags: '',
page: 1
});
this.hide();
},
_onClickLink: function (event) {
var tag = $(event.target).text();
this.collection.options.set({
tags: tag,
page: 1
});
this.hide();
}
});
@@ -0,0 +1,11 @@
<ul class="SettingsDropdown CDB-Size-medium">
<li class="SettingsDropdown-item SettingsDropdown-item--public">
<p><button class="SettingsDropdown-itemLink SettingsDropdown-itemLink--public js-all">All categories</button></p>
<p><button class="SettingsDropdown-itemLink SettingsDropdown-itemLink--public js-categoryLink">Administrative regions</button></p>
<p><button class="SettingsDropdown-itemLink SettingsDropdown-itemLink--public js-categoryLink">Cultural datasets</button></p>
<p><button class="SettingsDropdown-itemLink SettingsDropdown-itemLink--public js-categoryLink">Physical datasets</button></p>
<p><button class="SettingsDropdown-itemLink SettingsDropdown-itemLink--public js-categoryLink">Historic</button></p>
<p><button class="SettingsDropdown-itemLink SettingsDropdown-itemLink--public js-categoryLink">Building footprints</button></p>
<p><button class="SettingsDropdown-itemLink SettingsDropdown-itemLink--public js-categoryLink">US Census</button></p>
</li>
</ul>
@@ -0,0 +1,113 @@
const $ = require('jquery');
const CoreView = require('backbone/core-view');
const DropdownView = require('./dropdown/dropdown-view');
const Utils = require('builder/helpers/utils');
const template = require('./filters.tpl');
const ESC_KEY = 27;
/**
* Dashboard filters.
*
* - 'Filter by' collection.
* - 'Search' any pattern within collection.
*
*/
module.exports = CoreView.extend({
events: {
'submit .js-search-form': '_submitSearch',
'keydown .js-search-form': '_onSearchKeyDown',
'click .js-search-form': 'killEvent',
'click .js-search-link': '_onSearchClick',
'click .js-clean-search': '_onCleanSearchClick',
'click .js-categoriesDropdown': '_createDropdown'
},
initialize: function () {
this._preRender();
this._initBinds();
},
_preRender: function () {
const $uInner = $('<div>').addClass('u-inner');
const $filtersInner = $('<div>').addClass('Filters-inner');
this.$el.append($uInner.append($filtersInner));
},
render: function () {
this.$('.Filters-inner').html(
template({
tag: this.collection.options.get('tags'),
q: this.collection.options.get('q')
})
);
return this;
},
_initBinds: function () {
this.listenTo(this.collection, 'add remove change reset', this.render);
this.listenTo(this.collection.options, 'change:tags', this.render);
},
_createDropdown: function (event) {
this._setupDropdown(new DropdownView({
target: $(event.target).closest('.js-categoriesDropdown'),
tick: 'right',
collection: this.collection
}));
},
_setupDropdown: function (dropdownView) {
this.addView(dropdownView);
dropdownView.render();
dropdownView.open();
},
_onSearchClick: function (event) {
this.killEvent(event);
this.$('.js-search-input').val('');
this.$('.js-search-input').focus();
},
_onSearchKeyDown: function (event) {
if (event.code === ESC_KEY) {
this._onSearchClick(event);
}
},
// Filter actions
_onCleanSearchClick: function (event) {
this.killEvent(event);
this._cleanSearch();
},
_submitSearch: function (event) {
this.killEvent(event);
this.model.set('is_searching', true);
this.collection.options.set({
q: Utils.stripHTML(this.$('.js-search-input').val().trim(), ''),
page: 1
});
this.render();
},
_cleanSearch: function () {
this.model.set('is_searching', false);
this.collection.options.set({
q: '',
page: 1
});
this.render();
}
});
@@ -0,0 +1,33 @@
<span class="Filters-separator"></span>
<div class="Filters-row">
<div class="Filters-group <% if (q) { %>is-searching<% } %>">
<div class="Filters-typeItem Filters-typeItem--searchEnabler js-search-enabler">
<a href="#/search" class="Filters-searchLink CDB-Text CDB-Size-medium u-actionTextColor u-flex u-upperCase is-semibold js-search-link">
<div class="CDB-Shape u-rSpace">
<div class="CDB-Shape-magnify is-small is-blue"></div>
</div>
Search
</a>
</div>
<div class="Filters-typeItem Filters-typeItem--searchField js-search-field">
<form class="Filters-searchForm js-search-form" action="#">
<input class="Filters-searchInput CDB-Text CDB-Size-medium js-search-input" type="text" value="<%- q %>" placeholder="by name" />
<% if (q) { %>
<div class="CDB-Shape js-clean-search">
<div class="CDB-Shape-close is-blue is-large"></div>
</div>
<% } %>
</form>
</div>
</div>
<ul class="Filters-group">
<li class="Filters-typeItem <% if (q) { %>is-searching<% } %>">
<a class="Filters-typeLink CDB-Text CDB-Size-medium is-semibold u-upperCase js-categoriesDropdown" href="#/category">
<%- tag == '' ? 'Category' : tag %>
</a>
</li>
</ul>
</div>
@@ -0,0 +1,53 @@
const CoreView = require('backbone/core-view');
const $ = require('jquery');
const _ = require('underscore');
const L = require('leaflet');
const checkAndBuildOpts = require('builder/helpers/required-opts');
const template = require('./header.tpl');
const REQUIRED_OPTS = [
'configModel'
];
/**
* The header map in the data-library page, where the user can filter by country, e.g.:
*/
module.exports = CoreView.extend({
className: 'DataLibrary-header',
initialize: function (opts) {
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
_.bindAll(this, '_addGeojsonData');
},
render: function () {
this.$el.html(template());
return this;
},
load: function () {
this.map = L.map(this.$('#DataLibraryMap')[0], {
zoomControl: false,
attributionControl: false
}).setView([44, -31], 3);
var sqlDomain = this._configModel.get('sql_api_template').replace('{user}', this._configModel.get('common_data_user'));
var geojsonURL = sqlDomain + '/api/v2/sql?q=' + encodeURIComponent('select * from world_borders') + '&format=geojson&filename=world_borders';
$.getJSON(geojsonURL).done(this._addGeojsonData);
},
_addGeojsonData: function (geojsonData) {
var style = {
color: '#2E3C43',
weight: 1,
opacity: 1,
fillColor: '#242D32',
fillOpacity: 1
};
this.layer = L.geoJson(geojsonData, { style: style }).addTo(this.map);
}
});
@@ -0,0 +1,26 @@
<div class="DataLibrary-gradient"></div>
<div class="Header-inner Header-title js-Header-title u-inner">
<div class="Header-innerTitle">
<h1 class="Title-small">Data library</h1>
<p class="Title Title--l Title--white u-vspace-l">Open resources to help you populate your maps</p>
</div>
</div>
<div class="Header-inner Header-footer is-hidden">
<div class="CountrySelector js-CountrySelector u-inner">
<p class="CountrySelector-text">Filter datasets by country selecting it in this map</p>
<button class="CountrySelector-button js-country"></button>
</div>
<div class="CountrySelector CountrySelector-back js-CountrySelector-back is-hidden">
<div class="CountrySelector-inner u-inner">
<p class="CountrySelector-text">Back</p>
<button class="NavButton Dialog-countryBack js-back">
<i class="CDB-IconFont CDB-IconFont-close"></i>
</button>
</div>
</div>
</div>
<div id="DataLibraryMap" class="DataLibraryMap"></div>