Initial commit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<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="24px" viewbox="459 348 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Export-dataset" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(459.000000, 348.000000)">
|
||||
<path d="M1,1 L11,1 L11,5.5 C11,5.776 11.224,6 11.5,6 L16,6 L16,9.5 L17,9.5 L17,5.502 C17,5.502 17,5.502 17,5.501 C17,5.5 17,5.501 17,5.5 C17,5.447 16.985,5.398 16.97,5.35 C16.966,5.337 16.967,5.323 16.962,5.311 C16.936,5.247 16.896,5.19 16.847,5.142 L11.854,0.147 C11.808,0.101 11.753,0.064 11.692,0.039 C11.632,0.014 11.567,0 11.5,0 L0.5,0 C0.224,0 0,0.224 0,0.5 L0,21.5 C0,21.776 0.224,22 0.5,22 L10.5,22 L10.5,21 L1,21 L1,1 L1,1 Z M12,1.708 L15.291,5 L12,5 L12,1.708 L12,1.708 Z" id="Shape" fill="#2E3C43"/>
|
||||
<path d="M17.5,11 C13.916,11 11,13.916 11,17.5 C11,21.084 13.916,24 17.5,24 C21.084,24 24,21.084 24,17.5 C24,13.916 21.084,11 17.5,11 L17.5,11 Z M17.5,23 C14.467,23 12,20.533 12,17.5 C12,14.467 14.467,12 17.5,12 C20.533,12 23,14.467 23,17.5 C23,20.533 20.533,23 17.5,23 L17.5,23 Z" id="Shape" fill="#2E3C43"/>
|
||||
<path d="M17.858,14.425 C17.767,14.331 17.641,14.272 17.5,14.272 C17.359,14.272 17.232,14.331 17.142,14.425 L14.965,16.602 C14.77,16.797 14.77,17.114 14.965,17.309 C15.16,17.504 15.477,17.504 15.672,17.309 L17,15.981 L17,20.226 C17,20.502 17.224,20.726 17.5,20.726 C17.776,20.726 18,20.502 18,20.226 L18,15.981 L19.328,17.309 C19.426,17.407 19.554,17.455 19.682,17.455 C19.81,17.455 19.938,17.406 20.036,17.309 C20.231,17.114 20.231,16.797 20.036,16.602 L17.858,14.425 L17.858,14.425 Z" id="Shape" fill="#2E3C43" transform="translate(17.500500, 17.499000) scale(1, -1) translate(-17.500500, -17.499000) "/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--xl"><%- _t('editor.maps.export.confirmation.title', { name: name }) %></h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('editor.maps.export.confirmation.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.maps.export.confirmation.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.maps.export.confirmation.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="Dialog-header">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--neutral">
|
||||
<i class="CDB-IconFont CDB-IconFont-download"></i>
|
||||
</div>
|
||||
<h2 class="CDB-Text CDB-Size-large u-bSpace">
|
||||
<%- _t('editor.maps.export.download.title') %>
|
||||
</h2>
|
||||
<h3 class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
<%- _t('editor.maps.export.download.tip') %></br>
|
||||
<%- _t('editor.maps.export.download.desc') %>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="Dialog-footer--simple u-inner">
|
||||
<button class="CDB-Button CDB-Button--primary u-tSpace--m js-download">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">
|
||||
<%- _t('editor.maps.export.download.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,130 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var renderLoading = require('builder/components/loading/render-loading.js');
|
||||
var templateExportMapConfirmation = require('builder/editor/components/modals/export-map/export-map-confirmation.tpl');
|
||||
var templateExportMapDownload = require('builder/editor/components/modals/export-map/export-map-download.tpl');
|
||||
var ErrorView = require('builder/components/error/error-view.js');
|
||||
|
||||
var ENTER_KEY_CODE = 13;
|
||||
var REQUIRED_OPTS = [
|
||||
'renderOpts',
|
||||
'modalModel',
|
||||
'exportMapModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Dialog-content',
|
||||
|
||||
events: {
|
||||
'click .js-confirm': '_onConfirm',
|
||||
'click .js-cancel': '_onCancel',
|
||||
'click .js-download': '_download'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (!opts[item]) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var state = this._exportMapModel.get('state');
|
||||
|
||||
if (state === 'complete') {
|
||||
this._renderCompleteView();
|
||||
} else if (state === 'pending' || state === 'exporting' || state === 'uploading') {
|
||||
this._renderLoadingView(state);
|
||||
} else if (state === 'failure') {
|
||||
this._renderFailureView();
|
||||
} else {
|
||||
this._renderConfirmationView();
|
||||
}
|
||||
},
|
||||
|
||||
_renderCompleteView: function () {
|
||||
var w = window.open(this._exportMapModel.get('url'));
|
||||
|
||||
// If w is nullish, popup was blocked: we show a "click to download" modal. Else, download has started.
|
||||
if (w == null) {
|
||||
this.$el.html(templateExportMapDownload());
|
||||
} else {
|
||||
w.focus();
|
||||
this._modalModel.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
_renderLoadingView: function (state) {
|
||||
var loadingTitle = state.charAt(0).toUpperCase() + state.slice(1) + ' ...';
|
||||
|
||||
this.$el.html(
|
||||
renderLoading({
|
||||
title: loadingTitle
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_renderFailureView: function () {
|
||||
var errorView = new ErrorView({
|
||||
title: _t('editor.maps.export.error.title'),
|
||||
desc: _t('editor.maps.export.error.desc')
|
||||
});
|
||||
|
||||
this.$el.html(errorView.render().el);
|
||||
},
|
||||
|
||||
_renderConfirmationView: function () {
|
||||
this.$el.html(
|
||||
templateExportMapConfirmation({
|
||||
name: this._renderOpts.name
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
$(document).bind('keydown', this._onKeyDown);
|
||||
|
||||
this._exportMapModel.bind('change:state', function () {
|
||||
this.render();
|
||||
}, this);
|
||||
},
|
||||
|
||||
_disableBinds: function () {
|
||||
$(document).unbind('keydown', this._onKeyDown);
|
||||
},
|
||||
|
||||
_onKeyDown: function (ev) {
|
||||
var keyCode = ev.which;
|
||||
if (keyCode === ENTER_KEY_CODE) {
|
||||
this._onConfirm();
|
||||
}
|
||||
},
|
||||
|
||||
_onConfirm: function () {
|
||||
this._exportMapModel.requestExport();
|
||||
},
|
||||
|
||||
_onCancel: function () {
|
||||
this._exportMapModel.cancelExport();
|
||||
|
||||
this._modalModel.destroy();
|
||||
},
|
||||
|
||||
_download: function () {
|
||||
window.open(this._exportMapModel.get('url'));
|
||||
window.focus();
|
||||
|
||||
this._modalModel.destroy();
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._disableBinds();
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
});
|
||||
22
lib/assets/javascripts/builder/editor/components/overlay/overlay-view.js
Executable file
22
lib/assets/javascripts/builder/editor/components/overlay/overlay-view.js
Executable file
@@ -0,0 +1,22 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./overlay.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
if (!opts.overlayModel) throw new Error('overlayModel is required');
|
||||
this.overlayModel = opts.overlayModel;
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.empty();
|
||||
this.$el.append(template({
|
||||
visible: !this.overlayModel.get('visible') ? 'is-hidden' : ''
|
||||
}));
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.overlayModel, 'change:visible', this.render);
|
||||
}
|
||||
});
|
||||
1
lib/assets/javascripts/builder/editor/components/overlay/overlay.tpl
Executable file
1
lib/assets/javascripts/builder/editor/components/overlay/overlay.tpl
Executable file
@@ -0,0 +1 @@
|
||||
<div class="Disable-overlay <%- visible %>"></div>
|
||||
30
lib/assets/javascripts/builder/editor/delete-map-confirmation.tpl
Executable file
30
lib/assets/javascripts/builder/editor/delete-map-confirmation.tpl
Executable file
@@ -0,0 +1,30 @@
|
||||
<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.maps.delete.title', { name: name }) %></h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('editor.maps.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.maps.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.maps.delete.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
299
lib/assets/javascripts/builder/editor/editor-header.js
Executable file
299
lib/assets/javascripts/builder/editor/editor-header.js
Executable file
@@ -0,0 +1,299 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var _ = require('underscore');
|
||||
var template = require('./editor-header.tpl');
|
||||
var moment = require('moment');
|
||||
var ConfirmationView = require('builder/components/modals/confirmation/modal-confirmation-view');
|
||||
var EditMetadataView = require('builder/components/modals/map-metadata/map-metadata-view');
|
||||
var templateConfirmation = require('./delete-map-confirmation.tpl');
|
||||
var ExportView = require('builder/editor/components/modals/export-map/modal-export-map-view');
|
||||
var ExportMapModel = require('builder/data/export-map-definition-model.js');
|
||||
var removeMap = require('./map-operations/remove-map');
|
||||
var renameMap = require('./map-operations/rename-map');
|
||||
var InlineEditorView = require('builder/components/inline-editor/inline-editor-view');
|
||||
var templateInlineEditor = require('./inline-editor.tpl');
|
||||
var CreationModalView = require('builder/components/modals/creation/modal-creation-view');
|
||||
var VisDefinitionModel = require('builder/data/vis-definition-model');
|
||||
var errorParser = require('builder/helpers/error-parser');
|
||||
var ShareWith = require('builder/components/modals/publish/share-with-view');
|
||||
var ContextMenuFactory = require('builder/components/context-menu-factory-view');
|
||||
var PrivacyDropdown = require('builder/components/privacy-dropdown/privacy-dropdown-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'editorModel',
|
||||
'configModel',
|
||||
'modals',
|
||||
'visDefinitionModel',
|
||||
'privacyCollection',
|
||||
'mapcapsCollection',
|
||||
'clickPrivacyAction'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._title = this._visDefinitionModel.get('name');
|
||||
|
||||
_.bind(this._changeStyle, this);
|
||||
|
||||
this._bindEvents();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var model = this._privacyCollection.searchByPrivacy(this._visDefinitionModel.get('privacy'));
|
||||
var published = this._mapcapsCollection.length > 0
|
||||
? _t('editor.published', { when: moment(this._mapcapsCollection.first().get('created_at')).fromNow() })
|
||||
: _t('editor.unpublished');
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
title: this._title,
|
||||
privacy: model.get('privacy'),
|
||||
cssClass: model.get('cssClass'),
|
||||
avatar: this._userModel.get('avatar_url'),
|
||||
isInsideOrg: this._userModel.isInsideOrg(),
|
||||
published: published
|
||||
})
|
||||
);
|
||||
|
||||
this._initViews();
|
||||
this._changeStyle(this._editorModel);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var self = this;
|
||||
this._inlineEditor = new InlineEditorView({
|
||||
template: templateInlineEditor,
|
||||
renderOptions: {
|
||||
name: self._visDefinitionModel.get('name')
|
||||
},
|
||||
onEdit: self._renameMap.bind(self)
|
||||
});
|
||||
|
||||
this.$('.js-header').append(this._inlineEditor.render().el);
|
||||
this.addView(this._inlineEditor);
|
||||
|
||||
var shareWith = new ShareWith({
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
userModel: this._userModel,
|
||||
separationClass: 'u-rSpace--m',
|
||||
clickPrivacyAction: this._onClickPrivacy.bind(this)
|
||||
});
|
||||
|
||||
this.$('.js-share-users').append(shareWith.render().el);
|
||||
this.addView(shareWith);
|
||||
|
||||
var menuItems = [{
|
||||
label: _t('editor.maps.options.rename'),
|
||||
val: 'rename-map',
|
||||
action: this._onRenameMap.bind(this)
|
||||
}, {
|
||||
label: _t('editor.maps.options.duplicate'),
|
||||
val: 'duplicate-map',
|
||||
action: this._duplicateMap.bind(this),
|
||||
disabled: !this._canDuplicate()
|
||||
}, {
|
||||
label: _t('editor.maps.options.edit-metadata'),
|
||||
val: 'metadata-map',
|
||||
action: this._editMetadata.bind(this)
|
||||
}, {
|
||||
label: _t('editor.maps.options.export-map'),
|
||||
val: 'export-map',
|
||||
action: this._exportMap.bind(this)
|
||||
}, {
|
||||
label: _t('editor.maps.options.export-image'),
|
||||
val: 'export-image',
|
||||
action: this._exportImage.bind(this)
|
||||
}, {
|
||||
label: _t('editor.maps.options.remove'),
|
||||
val: 'delete-map',
|
||||
destructive: true,
|
||||
action: this._confirmDeleteLayer.bind(this)
|
||||
}];
|
||||
|
||||
this._contextMenuFactory = new ContextMenuFactory({
|
||||
menuItems: menuItems
|
||||
});
|
||||
|
||||
this.$('.js-context-menu').append(this._contextMenuFactory.render().el);
|
||||
this.addView(this._contextMenuFactory);
|
||||
|
||||
var privacyDropdown = new PrivacyDropdown({
|
||||
privacyCollection: this._privacyCollection,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel,
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
isOwner: true
|
||||
});
|
||||
|
||||
this.$('.js-dropdown').append(privacyDropdown.render().el);
|
||||
this.addView(privacyDropdown);
|
||||
},
|
||||
|
||||
_canDuplicate: function () {
|
||||
var isIndividualUser = this.options.userModel.isIndividualUser();
|
||||
var hasRemainingPublicMaps = this.options.userModel.hasRemainingPublicMaps();
|
||||
var privacy = this._visDefinitionModel.get('privacy');
|
||||
if (isIndividualUser && !hasRemainingPublicMaps && privacy !== 'PRIVATE') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_bindEvents: function () {
|
||||
this.listenTo(this._visDefinitionModel, 'change:privacy change:name', this.render);
|
||||
this.add_related_model(this._visDefinitionModel);
|
||||
|
||||
this.listenTo(this._editorModel, 'change:edition', this._changeStyle);
|
||||
this.add_related_model(this._editorModel);
|
||||
|
||||
this._mapcapsCollection.on('add reset', this.render, this);
|
||||
this._mapcapsCollection.on('change:status', this.render, this);
|
||||
this.add_related_model(this._mapcapsCollection);
|
||||
},
|
||||
|
||||
_renameMap: function () {
|
||||
var newName = this._inlineEditor.getValue();
|
||||
|
||||
if (newName !== '' && newName !== this._visDefinitionModel.get('name')) {
|
||||
// Speed!
|
||||
this._onRenameSuccess(newName);
|
||||
|
||||
renameMap({
|
||||
newName: newName,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
onError: this._onRenameError.bind(this)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onRenameMap: function () {
|
||||
this._inlineEditor.edit();
|
||||
},
|
||||
|
||||
_duplicateMap: function () {
|
||||
var self = this;
|
||||
var mapName = this._visDefinitionModel.get('name');
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return new CreationModalView({
|
||||
modalModel: modalModel,
|
||||
loadingTitle: _t('editor.maps.duplicate.loading', { name: mapName }),
|
||||
errorTitle: _t('editor.maps.duplicate.error', { name: mapName }),
|
||||
runAction: function (opts) {
|
||||
var newVisModel = new VisDefinitionModel({
|
||||
name: mapName
|
||||
}, {
|
||||
configModel: self._configModel
|
||||
});
|
||||
|
||||
newVisModel.save({
|
||||
source_visualization_id: self._visDefinitionModel.get('id')
|
||||
}, {
|
||||
success: function (visModel) {
|
||||
window.location = visModel.builderURL();
|
||||
},
|
||||
error: function (mdl, e) {
|
||||
opts.error && opts.error(errorParser(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_confirmDeleteLayer: function () {
|
||||
var self = this;
|
||||
var mapName = this._visDefinitionModel.get('name');
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return new ConfirmationView({
|
||||
modalModel: modalModel,
|
||||
template: templateConfirmation,
|
||||
loadingTitle: _t('editor.maps.delete.loading', {name: mapName}),
|
||||
renderOpts: {
|
||||
name: mapName
|
||||
},
|
||||
runAction: function () {
|
||||
removeMap({
|
||||
onSuccess: self._onSuccessDestroyMap.bind(self, modalModel),
|
||||
onError: self._onErrorDestroyMap.bind(self, modalModel),
|
||||
visDefinitionModel: self._visDefinitionModel
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_exportMap: function () {
|
||||
var mapName = this._visDefinitionModel.get('name');
|
||||
var visID = this._visDefinitionModel.get('id');
|
||||
|
||||
var exportMapModel = new ExportMapModel({
|
||||
visualization_id: visID
|
||||
}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return new ExportView({
|
||||
modalModel: modalModel,
|
||||
exportMapModel: exportMapModel,
|
||||
renderOpts: {
|
||||
name: mapName
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_exportImage: function () {
|
||||
this.trigger('export-image', this);
|
||||
},
|
||||
|
||||
_editMetadata: function () {
|
||||
var self = this;
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return new EditMetadataView({
|
||||
modalModel: modalModel,
|
||||
visDefinitionModel: self._visDefinitionModel
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onSuccessDestroyMap: function (modal) {
|
||||
this.options.onRemoveMap && this.options.onRemoveMap();
|
||||
},
|
||||
|
||||
_onErrorDestroyMap: function (modal) {
|
||||
modal.destroy();
|
||||
},
|
||||
|
||||
_onRenameSuccess: function (newName) {
|
||||
this.$('.js-title').text(newName).show();
|
||||
this._inlineEditor.hide();
|
||||
},
|
||||
|
||||
_onRenameError: function (oldName) {
|
||||
this.$('.js-title').text(oldName).show();
|
||||
this._inlineEditor.hide();
|
||||
},
|
||||
|
||||
_changeStyle: function (m) {
|
||||
this._getTitle().toggleClass('is-dark', m.isEditing());
|
||||
},
|
||||
|
||||
_getTitle: function () {
|
||||
return this.$('.Editor-HeaderInfo');
|
||||
},
|
||||
|
||||
_onClickPrivacy: function () {
|
||||
this.options.clickPrivacyAction && this.options.clickPrivacyAction();
|
||||
}
|
||||
});
|
||||
15
lib/assets/javascripts/builder/editor/editor-header.tpl
Executable file
15
lib/assets/javascripts/builder/editor/editor-header.tpl
Executable file
@@ -0,0 +1,15 @@
|
||||
<div class="Editor-HeaderInfoEditor">
|
||||
<div class="Editor-HeaderInfo-inner Editor-HeaderInfo-inner--wide">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m js-context-menu">
|
||||
<div class="Editor-HeaderInfo-titleText u-bSpace js-header">
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-bSpace--xl u-flex u-alignCenter">
|
||||
<div class="js-dropdown u-rSpace--m"></div>
|
||||
<% if (isInsideOrg) { %>
|
||||
<div class="js-share-users"></div>
|
||||
<% } %>
|
||||
<p class="Editor-HeaderInfo-publishDate u-ellipsis CDB-Text CDB-Size-medium u-altTextColor"><%- published %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
25
lib/assets/javascripts/builder/editor/editor-hints/css-hints.js
Executable file
25
lib/assets/javascripts/builder/editor/editor-hints/css-hints.js
Executable file
@@ -0,0 +1,25 @@
|
||||
module.exports = [{
|
||||
keywords: 'buffer-size',
|
||||
type: 'Map'
|
||||
}, {
|
||||
keywords: 'marker-fill marker-width marker-fill-opacity marker-opacity marker-line-color marker-line-width marker-line-opacity marker-comp-op marker-allow-overlap marker-file marker-type marker-height marker-width marker-transform marker-placement marker-ignore-placement marker-spacing marker-avoid-edges point-file point-opacity point-comp-op point-allow-overlap point-placement point-ignore-placement point-transform',
|
||||
type: 'Points'
|
||||
}, {
|
||||
keywords: 'line-color line-width line-opacity line-comp-op line-pattern-file line-join line-cap line-dasharray line-dash-offset line-offset line-simplify line-simplify-algorithm line-smooth line-clip',
|
||||
type: 'Lines'
|
||||
}, {
|
||||
keywords: 'polygon-fill polygon-opacity polygon-comp-op polygon-pattern-file polygon-pattern-alignment polygon-pattern-opacity polygon-smooth polygon-simplify polygon-simplify-algorithm polygon-gamma polygon-gamma-method polygon-geometry-transform polygon-clip',
|
||||
type: 'Polygons'
|
||||
}, {
|
||||
keywords: 'text-name text-face-name text-size text-fill text-opacity text-comp-op text-halo-radius text-halo-fill text-halo-rasterizer text-allow-overlap text-transform text-placement text-placement-type text-dx text-dy text-wrap-width text-wrap-before text-character-spacing text-line-spacing text-min-distance text-avoid-edges text-orientation text-vertical-alignment',
|
||||
type: 'Text'
|
||||
}, {
|
||||
keywords: 'building-fill building-fill-opacity building-height',
|
||||
type: 'Building'
|
||||
}, {
|
||||
keywords: 'shield-name shield-face-name shield-size shield-file shield-fill shield-opacity shield-text-opacity shield-allow-overlap shield-placements shield-placement-type shield-dy shield-dx shield-text-dx shield-text-dy shield-min-distance shield-avoid-edges',
|
||||
type: 'Shield'
|
||||
}, {
|
||||
keywords: 'image-filters comp-op opacity',
|
||||
type: ''
|
||||
}];
|
||||
96
lib/assets/javascripts/builder/editor/editor-hints/factory-hints.js
Executable file
96
lib/assets/javascripts/builder/editor/editor-hints/factory-hints.js
Executable file
@@ -0,0 +1,96 @@
|
||||
var _ = require('underscore');
|
||||
var TableNameUtils = require('builder/helpers/table-name-utils');
|
||||
|
||||
var memo = {};
|
||||
|
||||
var OPTIONAL_OPTS = [
|
||||
'querySchemaModel',
|
||||
'layerDefinitionModel',
|
||||
'tokens'
|
||||
];
|
||||
|
||||
var defaults = {
|
||||
tableName: true,
|
||||
columnsName: true,
|
||||
keywords: true
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init: function (opts) {
|
||||
if (!opts) throw new Error('options are required for hints');
|
||||
|
||||
this.options = _.defaults(
|
||||
_.pick(opts, ['tableName', 'columnsName', 'keywords'])
|
||||
, defaults
|
||||
);
|
||||
|
||||
_.each(OPTIONAL_OPTS, function (item) {
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
},
|
||||
|
||||
hints: function () {
|
||||
// array of objects {text, type}
|
||||
return this.hints;
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
this.hints = _.union(this._getTableName(), this._getColumns(), this._getKeywords());
|
||||
return this;
|
||||
},
|
||||
|
||||
_getKeywords: function () {
|
||||
if (this.options.keywords) {
|
||||
return this._tokens && this._memo(this._tokens);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
_getColumns: function () {
|
||||
if (!this.options.columnsName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var columns = this._querySchemaModel.columnsCollection;
|
||||
return columns.map(function (mdl) {
|
||||
var columnName = mdl.get('name');
|
||||
return {
|
||||
text: columnName,
|
||||
type: 'Column_name'
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
_getTableName: function () {
|
||||
if (!this.options.tableName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var tableName = this._layerDefinitionModel.getTableName();
|
||||
tableName = TableNameUtils.getUnqualifiedName(tableName);
|
||||
return [{
|
||||
text: tableName,
|
||||
type: 'Table_name'
|
||||
}];
|
||||
},
|
||||
|
||||
_memo: function (str, type) {
|
||||
if (str in memo) {
|
||||
return memo[str];
|
||||
} else {
|
||||
return (memo[str] = this._make(str, type));
|
||||
}
|
||||
},
|
||||
|
||||
_make: function (str, type) {
|
||||
return _.chain(str).map(function (set) {
|
||||
return set.keywords.split(' ').map(function (item) {
|
||||
return {
|
||||
text: item,
|
||||
type: set.type
|
||||
};
|
||||
});
|
||||
}).flatten().value();
|
||||
}
|
||||
};
|
||||
7
lib/assets/javascripts/builder/editor/editor-hints/sql-hints.js
Executable file
7
lib/assets/javascripts/builder/editor/editor-hints/sql-hints.js
Executable file
@@ -0,0 +1,7 @@
|
||||
module.exports = [{
|
||||
keywords: 'alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit',
|
||||
type: 'Sql'
|
||||
}, {
|
||||
keywords: 'ST_GeometryType ST_polygon ST_multipolygon ST_multilinestring ST_linestring ST_multipoint ST_point',
|
||||
type: 'Postgis'
|
||||
}];
|
||||
279
lib/assets/javascripts/builder/editor/editor-map-view.js
Executable file
279
lib/assets/javascripts/builder/editor/editor-map-view.js
Executable file
@@ -0,0 +1,279 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var EditorView = require('./editor-view');
|
||||
var AnalysesService = require('./layers/layer-content-views/analyses/analyses-service');
|
||||
var StackLayoutView = require('builder/components/stack-layout/stack-layout-view');
|
||||
var BasemapContentView = require('./layers/basemap-content-view');
|
||||
var LayerContentView = require('./layers/layer-content-view');
|
||||
var EditFeatureContentView = require('./layers/edit-feature-content-view');
|
||||
var WidgetsFormContentView = require('./widgets/widgets-form/widgets-form-content-view');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var Router = require('builder/routes/router');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userActions',
|
||||
'basemaps',
|
||||
'visDefinitionModel',
|
||||
'layerDefinitionsCollection',
|
||||
'analysisDefinitionNodesCollection',
|
||||
'legendDefinitionsCollection',
|
||||
'widgetDefinitionsCollection',
|
||||
'mapcapsCollection',
|
||||
'privacyCollection',
|
||||
'modals',
|
||||
'onboardings',
|
||||
'userModel',
|
||||
'configModel',
|
||||
'editorModel',
|
||||
'pollingModel',
|
||||
'mapDefinitionModel',
|
||||
'mapModeModel',
|
||||
'stateDefinitionModel',
|
||||
'onboardingNotification',
|
||||
'settingsCollection',
|
||||
'routeModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'MapEditor Editor-panel',
|
||||
|
||||
events: {
|
||||
'click .js-add-analysis': '_onAddAnalysisClicked'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
var self = this;
|
||||
|
||||
var stackViewCollection = new Backbone.Collection([{
|
||||
createStackView: function (stackLayoutModel, opts) {
|
||||
var selectedTabItem = opts[0] || 'layers';
|
||||
|
||||
return new EditorView({
|
||||
className: 'Editor-content',
|
||||
modals: self._modals,
|
||||
userModel: self._userModel,
|
||||
userActions: self._userActions,
|
||||
configModel: self._configModel,
|
||||
editorModel: self._editorModel,
|
||||
pollingModel: self._pollingModel,
|
||||
mapDefinitionModel: self._mapDefinitionModel,
|
||||
visDefinitionModel: self._visDefinitionModel,
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
widgetDefinitionsCollection: self._widgetDefinitionsCollection,
|
||||
mapcapsCollection: self._mapcapsCollection,
|
||||
privacyCollection: self._privacyCollection,
|
||||
mapStackLayoutModel: stackLayoutModel,
|
||||
selectedTabItem: selectedTabItem,
|
||||
mapModeModel: self._mapModeModel,
|
||||
stateDefinitionModel: self._stateDefinitionModel,
|
||||
settingsCollection: self._settingsCollection,
|
||||
routeModel: self._routeModel
|
||||
});
|
||||
}
|
||||
}, {
|
||||
createStackView: function (stackLayoutModel, opts) {
|
||||
var viewType = opts[1];
|
||||
|
||||
switch (viewType) {
|
||||
case 'basemaps':
|
||||
return new BasemapContentView({
|
||||
className: 'Editor-content',
|
||||
basemaps: self._basemaps,
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
stackLayoutModel: stackLayoutModel,
|
||||
customBaselayersCollection: self._userModel.layers,
|
||||
modals: self._modals,
|
||||
configModel: self._configModel
|
||||
});
|
||||
case 'layer-content':
|
||||
var layerDefinitionModel = opts[0];
|
||||
var selectedTabItem = opts[2];
|
||||
var analysisPayload = opts[3];
|
||||
|
||||
return new LayerContentView({
|
||||
className: 'Editor-content',
|
||||
userActions: self._userActions,
|
||||
userModel: self._userModel,
|
||||
layerDefinitionModel: layerDefinitionModel,
|
||||
mapDefinitionModel: self._mapDefinitionModel,
|
||||
widgetDefinitionsCollection: self._widgetDefinitionsCollection,
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
legendDefinitionsCollection: self._legendDefinitionsCollection,
|
||||
analysis: self._analysis,
|
||||
modals: self._modals,
|
||||
onboardings: self._onboardings,
|
||||
stackLayoutModel: stackLayoutModel,
|
||||
analysisPayload: analysisPayload,
|
||||
configModel: self._configModel,
|
||||
editorModel: self._editorModel,
|
||||
mapModeModel: self._mapModeModel,
|
||||
stateDefinitionModel: self._stateDefinitionModel,
|
||||
onboardingNotification: self._onboardingNotification,
|
||||
visDefinitionModel: self._visDefinitionModel,
|
||||
selectedTabItem: selectedTabItem
|
||||
});
|
||||
case 'widget-content':
|
||||
var widgetDefinitionModel = opts[0];
|
||||
var stackLayoutPrevStep = opts[1];
|
||||
|
||||
return new WidgetsFormContentView({
|
||||
className: 'Editor-content',
|
||||
modals: self._modals,
|
||||
userModel: self._userModel,
|
||||
configModel: self._configModel,
|
||||
userActions: self._userActions,
|
||||
widgetDefinitionModel: widgetDefinitionModel,
|
||||
widgetDefinitionsCollection: self._widgetDefinitionsCollection,
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
stackLayoutPrevStep: stackLayoutPrevStep,
|
||||
stackLayoutModel: stackLayoutModel
|
||||
});
|
||||
case 'element-content':
|
||||
console.log(viewType + 'view is not implemented yet');
|
||||
break;
|
||||
default:
|
||||
console.log(viewType + 'view doesn\'t exist');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
createStackView: function (stackLayoutModel, opts) {
|
||||
var layerDefinitionModel = opts[0];
|
||||
var isValidGeometry = opts[1];
|
||||
|
||||
return new EditFeatureContentView({
|
||||
layerDefinitionModel: layerDefinitionModel,
|
||||
configModel: self._configModel,
|
||||
stackLayoutModel: stackLayoutModel,
|
||||
mapModeModel: self._mapModeModel,
|
||||
editorModel: self._editorModel,
|
||||
model: new Backbone.Model({
|
||||
hasChanges: false,
|
||||
isValidAttributes: true,
|
||||
isValidGeometry: isValidGeometry
|
||||
}),
|
||||
modals: self._modals
|
||||
});
|
||||
}
|
||||
}]);
|
||||
|
||||
this._stackLayoutView = new StackLayoutView({
|
||||
className: 'Editor-content',
|
||||
collection: stackViewCollection
|
||||
});
|
||||
|
||||
this.$el.append(this._stackLayoutView.render().$el);
|
||||
this.addView(this._stackLayoutView);
|
||||
|
||||
var notifierView = Notifier.getView();
|
||||
this.$el.append(notifierView.render().el);
|
||||
this.addView(notifierView);
|
||||
|
||||
this._setServices();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_setServices: function () {
|
||||
AnalysesService.setStackLayoutView(this._stackLayoutView);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._routeModel, 'change:currentRoute', this._handleRoute);
|
||||
this.listenTo(this._editorModel, 'change:edition', this._changeStyle);
|
||||
},
|
||||
|
||||
// Required to receive external route from parent
|
||||
handleRoute: function (routeModel) {
|
||||
this._handleRoute(routeModel);
|
||||
},
|
||||
|
||||
_handleRoute: function (routeModel) {
|
||||
var currentRoute = routeModel.get('currentRoute');
|
||||
var routeName = currentRoute[0];
|
||||
|
||||
if (routeName.indexOf('layer_') === 0) {
|
||||
var layerId = currentRoute[1];
|
||||
var layerDefinitionModel = this._layerDefinitionsCollection.find({ id: layerId });
|
||||
|
||||
if (layerDefinitionModel === undefined) {
|
||||
Router.goToLayerList();
|
||||
return true;
|
||||
}
|
||||
|
||||
var args = [1, layerDefinitionModel, 'layer-content'];
|
||||
switch (routeName) {
|
||||
case 'layer_data':
|
||||
args.push('data');
|
||||
break;
|
||||
case 'layer_style':
|
||||
args.push('style');
|
||||
break;
|
||||
case 'layer_analyses':
|
||||
args.push('analyses');
|
||||
args.push(currentRoute[2]);
|
||||
break;
|
||||
case 'layer_popups':
|
||||
args.push('popups');
|
||||
break;
|
||||
case 'layer_legends':
|
||||
args.push('legends');
|
||||
break;
|
||||
}
|
||||
|
||||
this._stackLayoutView.model.goToStep.apply(this._stackLayoutView.model, args);
|
||||
return true;
|
||||
} else if (routeName === 'widget') {
|
||||
var widgetId = currentRoute[1];
|
||||
var widgetDefinitionModel = this._widgetDefinitionsCollection.find({ id: widgetId });
|
||||
|
||||
if (widgetDefinitionModel === undefined) {
|
||||
Router.goToWidgetList();
|
||||
return true;
|
||||
}
|
||||
|
||||
this._stackLayoutView.model.goToStep(1, widgetDefinitionModel, 'widget-content');
|
||||
return true;
|
||||
} else if (routeName === 'root' || routeName === 'layers' || routeName === 'widgets') {
|
||||
this._stackLayoutView.model.goToStep(0, routeName);
|
||||
return true;
|
||||
} else if (routeName === 'edit_feature') {
|
||||
var feature = this._mapModeModel.getFeatureDefinition();
|
||||
|
||||
if (feature) {
|
||||
var isFeatureNew = !feature.isNew();
|
||||
this._stackLayoutView.model.goToStep(2, this._layerDefinitionsCollection.find({ id: currentRoute[1] }), isFeatureNew);
|
||||
} else {
|
||||
// We could instantiate a feature here based on the URL parameters, this is easier for now
|
||||
Router.goToPreviousRoute({
|
||||
options: { replace: true }
|
||||
});
|
||||
}
|
||||
} else if (routeName === 'basemap') {
|
||||
this._stackLayoutView.model.goToStep(1, null, 'basemaps');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_changeStyle: function () {
|
||||
this.$el.toggleClass('is-dark', this._editorModel.isEditing());
|
||||
},
|
||||
|
||||
_onAddAnalysisClicked: function () {
|
||||
AnalysesService.addAnalysis();
|
||||
}
|
||||
});
|
||||
1
lib/assets/javascripts/builder/editor/editor-pane-icon.tpl
Executable file
1
lib/assets/javascripts/builder/editor/editor-pane-icon.tpl
Executable file
@@ -0,0 +1 @@
|
||||
<i class="CDB-IconFont CDB-IconFont-<%- icon %> EditorMenu-navigationIcon"></i>
|
||||
443
lib/assets/javascripts/builder/editor/editor-pane.js
Executable file
443
lib/assets/javascripts/builder/editor/editor-pane.js
Executable file
@@ -0,0 +1,443 @@
|
||||
var $ = require('jquery');
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var createTextLabelsTabPaneRouted = require('builder/components/tab-pane/create-text-labels-tab-pane-routed');
|
||||
var Header = require('./editor-header.js');
|
||||
var EditorTabPaneTemplate = require('./editor-tab-pane.tpl');
|
||||
var EditorWidgetsView = require('./widgets/widgets-view');
|
||||
var LayersView = require('./layers/layers-view');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var PanelWithOptionsView = require('builder/components/view-options/panel-with-options-view');
|
||||
var ShareButtonView = require('./layers/share-button-view');
|
||||
var PublishView = require('builder/components/modals/publish/publish-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var Infobox = require('builder/components/infobox/infobox-factory');
|
||||
var InfoboxModel = require('builder/components/infobox/infobox-model');
|
||||
var InfoboxCollection = require('builder/components/infobox/infobox-collection');
|
||||
var AppNotifications = require('builder/app-notifications');
|
||||
var zoomToData = require('./map-operations/zoom-to-data');
|
||||
var Router = require('builder/routes/router');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userActions',
|
||||
'modals',
|
||||
'configModel',
|
||||
'userModel',
|
||||
'editorModel',
|
||||
'pollingModel',
|
||||
'analysisDefinitionNodesCollection',
|
||||
'layerDefinitionsCollection',
|
||||
'privacyCollection',
|
||||
'widgetDefinitionsCollection',
|
||||
'mapcapsCollection',
|
||||
'visDefinitionModel',
|
||||
'stateDefinitionModel',
|
||||
'selectedTabItem',
|
||||
'mapStackLayoutModel',
|
||||
'routeModel'
|
||||
];
|
||||
|
||||
var LAYERS_TAB_NAME = 'layers';
|
||||
var WIDGETS_TAB_NAME = 'widgets';
|
||||
var MAX_LAYERS_REACHED = 'MAX_LAYERS_REACHED';
|
||||
var ORIGIN_TABLE = 'table';
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Editor-content',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initModels();
|
||||
this._initBinds();
|
||||
|
||||
this._zoomToData = zoomToData; // For using Dependency Injection in specs
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var self = this;
|
||||
|
||||
var count = this._getDataLayerCount();
|
||||
var max = this._getMaxCount();
|
||||
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
var infoboxStates = [
|
||||
this._getMaxLayerInfoBoxForCurrentUser(),
|
||||
this._getUnknownErrorInfobox(),
|
||||
this._getLimitInfobox('interactivity'),
|
||||
this._getLimitInfobox('limit')
|
||||
];
|
||||
|
||||
this._infoboxModel = new InfoboxModel({
|
||||
state: null
|
||||
});
|
||||
|
||||
var infoboxCollection = new InfoboxCollection(infoboxStates);
|
||||
|
||||
var tabPaneTabs = [{
|
||||
name: LAYERS_TAB_NAME,
|
||||
label: this._getTranslatedLayersLabel(count, max),
|
||||
selected: this._selectedTabItem === LAYERS_TAB_NAME,
|
||||
onClick: function () { Router.goToLayerList(); },
|
||||
createContentView: function () {
|
||||
return new PanelWithOptionsView({
|
||||
className: 'Editor-content js-editorPanelContent',
|
||||
editorModel: self._editorModel,
|
||||
infoboxModel: self._infoboxModel,
|
||||
infoboxCollection: infoboxCollection,
|
||||
createContentView: function () {
|
||||
return new ScrollView({
|
||||
createContentView: function () {
|
||||
var scrollViewInstance = this;
|
||||
return new LayersView({
|
||||
modals: self._modals,
|
||||
userModel: self._userModel,
|
||||
editorModel: self._editorModel,
|
||||
configModel: self._configModel,
|
||||
pollingModel: self._pollingModel,
|
||||
userActions: self._userActions,
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
stateDefinitionModel: self._stateDefinitionModel,
|
||||
widgetDefinitionsCollection: self._widgetDefinitionsCollection,
|
||||
visDefinitionModel: self._visDefinitionModel,
|
||||
showMaxLayerError: self._infoboxState.bind(self),
|
||||
onNotificationCloseAction: scrollViewInstance._updateScrollWhenExist.bind(scrollViewInstance)
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
createActionView: function () {
|
||||
return new ShareButtonView({
|
||||
visDefinitionModel: self._visDefinitionModel,
|
||||
onClickAction: self._share.bind(self)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
name: 'widgets',
|
||||
label: _t('editor.tab-pane.widgets.title-label'),
|
||||
selected: this._selectedTabItem === WIDGETS_TAB_NAME,
|
||||
onClick: function () { Router.goToWidgetList(); },
|
||||
createContentView: function () {
|
||||
return new PanelWithOptionsView({
|
||||
className: 'Editor-content',
|
||||
editorModel: self._editorModel,
|
||||
infoboxModel: self._infoboxModel,
|
||||
infoboxCollection: infoboxCollection,
|
||||
createContentView: function () {
|
||||
return new ScrollView({
|
||||
createContentView: function () {
|
||||
return new EditorWidgetsView({
|
||||
userActions: self._userActions,
|
||||
modals: self._modals,
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
widgetDefinitionsCollection: self._widgetDefinitionsCollection,
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
userModel: self._userModel,
|
||||
configModel: self._configModel,
|
||||
stackLayoutModel: self._mapStackLayoutModel
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
createActionView: function () {
|
||||
return new ShareButtonView({
|
||||
visDefinitionModel: self._visDefinitionModel,
|
||||
onClickAction: self._share.bind(self)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
var header = new Header({
|
||||
editorModel: self._editorModel,
|
||||
mapcapsCollection: self._mapcapsCollection,
|
||||
modals: self._modals,
|
||||
visDefinitionModel: self._visDefinitionModel,
|
||||
privacyCollection: self._privacyCollection,
|
||||
clickPrivacyAction: self._share.bind(self),
|
||||
onRemoveMap: self._onRemoveMap.bind(self),
|
||||
configModel: self._configModel,
|
||||
userModel: self._userModel
|
||||
});
|
||||
|
||||
header.bind('export-image', this._onExportImage, this);
|
||||
|
||||
this.$el.append(header.render().$el);
|
||||
this.addView(header);
|
||||
|
||||
var tabPaneOptions = {
|
||||
tabPaneOptions: {
|
||||
template: EditorTabPaneTemplate,
|
||||
tabPaneItemOptions: {
|
||||
tagName: 'li',
|
||||
klassName: 'CDB-NavMenu-item'
|
||||
}
|
||||
},
|
||||
tabPaneItemLabelOptions: {
|
||||
tagName: 'button',
|
||||
className: 'CDB-NavMenu-link u-upperCase'
|
||||
}
|
||||
};
|
||||
|
||||
this._mapTabPaneView = createTextLabelsTabPaneRouted(tabPaneTabs, tabPaneOptions);
|
||||
|
||||
this.$el.append(this._mapTabPaneView.render().$el);
|
||||
this.addView(this._mapTabPaneView);
|
||||
|
||||
this._infoboxState();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this._updatedModel = new Backbone.Model({
|
||||
date: ''
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._editorModel, 'change:edition', this._changeStyle);
|
||||
this.listenTo(this._layerDefinitionsCollection, 'add', this._onLayerAdd);
|
||||
this.listenTo(this._layerDefinitionsCollection, 'add remove', this._onLayerCountChange);
|
||||
this.listenTo(AppNotifications.getCollection(), 'add', this._infoboxState);
|
||||
this.listenTo(this._routeModel, 'change:currentRoute', this._handleRoute);
|
||||
},
|
||||
|
||||
_handleRoute: function (routeModel) {
|
||||
var currentRoute = routeModel.get('currentRoute');
|
||||
|
||||
if (!currentRoute) return;
|
||||
|
||||
var routeName = currentRoute[0];
|
||||
|
||||
if (routeName === WIDGETS_TAB_NAME) {
|
||||
this._mapTabPaneView.setSelectedTabPaneByName(WIDGETS_TAB_NAME);
|
||||
} else {
|
||||
this._mapTabPaneView.setSelectedTabPaneByName(LAYERS_TAB_NAME);
|
||||
}
|
||||
},
|
||||
|
||||
_onLayerAdd: function (model, collection, options) {
|
||||
if (options.origin && options.origin === ORIGIN_TABLE) {
|
||||
var nodeModel = model.getAnalysisDefinitionNodeModel();
|
||||
var query = nodeModel.querySchemaModel.get('query');
|
||||
|
||||
this._zoomToData(this._configModel, this._stateDefinitionModel, query);
|
||||
}
|
||||
|
||||
this._visDefinitionModel.fetch();
|
||||
},
|
||||
|
||||
_getMaxLayerTitle: function () {
|
||||
return _t('editor.layers.max-layers-infowindow.title');
|
||||
},
|
||||
|
||||
_getMaxLayerInfoBoxForCurrentUser: function () {
|
||||
var infoboxOpts = {
|
||||
type: 'alert',
|
||||
title: this._getMaxLayerTitle()
|
||||
};
|
||||
|
||||
var baseState = {
|
||||
state: MAX_LAYERS_REACHED
|
||||
};
|
||||
|
||||
// Open-source / local installation
|
||||
if (this._configModel.isHosted()) {
|
||||
infoboxOpts.body = _t('editor.layers.max-layers-infowindow.custom.body', { maxLayers: this._getMaxCount() });
|
||||
infoboxOpts.action = { label: _t('editor.layers.max-layers-infowindow.custom.contact') };
|
||||
baseState.onAction = function () { window.open(_t('editor.layers.max-layers-infowindow.custom.contact-url')); };
|
||||
} else {
|
||||
if (this._userModel.isInsideOrg()) {
|
||||
if (this._userModel.isOrgAdmin()) {
|
||||
infoboxOpts.body = _t('editor.layers.max-layers-infowindow.org-admin.body', { maxLayers: this._getMaxCount() });
|
||||
infoboxOpts.action = { label: _t('editor.layers.max-layers-infowindow.org-admin.upgrade') };
|
||||
} else {
|
||||
infoboxOpts.body = _t('editor.layers.max-layers-infowindow.org.body', { maxLayers: this._getMaxCount() });
|
||||
infoboxOpts.action = { label: _t('editor.layers.max-layers-infowindow.org.upgrade') };
|
||||
}
|
||||
|
||||
baseState.onAction = function () { window.open('mailto:' + this._userModel.upgradeContactEmail()); };
|
||||
} else {
|
||||
baseState.onAction = function () { window.open(_t('editor.layers.max-layers-infowindow.pricing')); };
|
||||
infoboxOpts.body = _t('editor.layers.max-layers-infowindow.regular.body', { maxLayers: this._getMaxCount() });
|
||||
infoboxOpts.action = { label: _t('editor.layers.max-layers-infowindow.regular.upgrade') };
|
||||
}
|
||||
}
|
||||
|
||||
return _.extend(baseState, {
|
||||
createContentView: function () {
|
||||
return Infobox.createWithAction(infoboxOpts);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_getUnknownErrorInfobox: function () {
|
||||
return {
|
||||
state: 'unknown',
|
||||
createContentView: function () {
|
||||
return Infobox.createWithAction({
|
||||
type: 'alert',
|
||||
title: _t('editor.messages.generic-error.title'),
|
||||
body: _t('editor.messages.generic-error.body'),
|
||||
className: 'fs-unknown-tile-error'
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
_getLimitInfobox: function (type) {
|
||||
var infoboxOpts = {
|
||||
type: 'alert',
|
||||
title: _t('editor.messages.' + type + '.title'),
|
||||
body: _t('editor.messages.' + type + '.body')
|
||||
};
|
||||
|
||||
var baseState = {
|
||||
state: type,
|
||||
onClose: function () {
|
||||
AppNotifications.muteByType(type);
|
||||
this._infoboxModel.set('state', null);
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
if (!this._configModel.isHosted()) {
|
||||
infoboxOpts.action = {
|
||||
label: _t('editor.messages.' + type + '.cta.label'),
|
||||
type: 'secondary'
|
||||
};
|
||||
|
||||
baseState.onAction = function () {
|
||||
window.open(_t('editor.messages.' + type + '.cta.url'));
|
||||
};
|
||||
|
||||
infoboxOpts.body = _t('editor.messages.' + type + '.body') + _t('editor.messages.' + type + '.try_to');
|
||||
}
|
||||
|
||||
return _.extend(baseState, {
|
||||
createContentView: function () {
|
||||
return Infobox.createWithAction(infoboxOpts);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onLayerCountChange: function () {
|
||||
var count = this._getDataLayerCount();
|
||||
var max = this._getMaxCount();
|
||||
|
||||
var layersModel = this._mapTabPaneView.getTabPaneCollection().find(function (model) {
|
||||
return model.get('name') === LAYERS_TAB_NAME;
|
||||
});
|
||||
|
||||
layersModel.set('label', this._getTranslatedLayersLabel(count, max));
|
||||
|
||||
if (count === max) {
|
||||
this._infoboxState();
|
||||
} else {
|
||||
this._infoboxModel.set('state', null);
|
||||
}
|
||||
},
|
||||
|
||||
_infoboxState: function () {
|
||||
var count = this._getDataLayerCount();
|
||||
var max = this._getMaxCount();
|
||||
var hasLimitError = AppNotifications.getByType('limit');
|
||||
var hasUnknownError = AppNotifications.getByType('unknown');
|
||||
var hasInteractivityError = AppNotifications.getByType('interactivity');
|
||||
|
||||
if (hasLimitError) {
|
||||
this._infoboxModel.set('state', 'limit');
|
||||
} else if (hasInteractivityError) {
|
||||
this._infoboxModel.set('state', 'interactivity');
|
||||
} else if (hasUnknownError) {
|
||||
this._infoboxModel.set('state', 'unknown');
|
||||
} else if (count === max) {
|
||||
this._infoboxModel.set('state', MAX_LAYERS_REACHED);
|
||||
} else {
|
||||
this._infoboxModel.set('state', null);
|
||||
}
|
||||
},
|
||||
|
||||
_onExportImage: function () {
|
||||
this.trigger('export-image', this);
|
||||
},
|
||||
|
||||
_onRemoveMap: function () {
|
||||
window.location = this._userModel.get('base_url');
|
||||
},
|
||||
|
||||
_getDataLayerCount: function () {
|
||||
return this._layerDefinitionsCollection.getNumberOfDataLayers();
|
||||
},
|
||||
|
||||
_getMaxCount: function () {
|
||||
return this._userModel.get('limits').max_layers;
|
||||
},
|
||||
|
||||
_getTranslatedLayersLabel: function (count, max) {
|
||||
return _t('editor.tab-pane.layers.title-label', {
|
||||
count: count,
|
||||
maxCount: max
|
||||
});
|
||||
},
|
||||
|
||||
_share: function () {
|
||||
var self = this;
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return new PublishView({
|
||||
mapcapsCollection: self._mapcapsCollection,
|
||||
modalModel: modalModel,
|
||||
visDefinitionModel: self._visDefinitionModel,
|
||||
privacyCollection: self._privacyCollection,
|
||||
userModel: self._userModel,
|
||||
configModel: self._configModel,
|
||||
isOwner: true
|
||||
});
|
||||
}, {
|
||||
breadcrumbsEnabled: true
|
||||
});
|
||||
},
|
||||
|
||||
_changeStyle: function (m) {
|
||||
this.$el.toggleClass('is-dark');
|
||||
this._mapTabPaneView.changeStyleMenu(m);
|
||||
},
|
||||
|
||||
_setUpdateFromCreation: function () {
|
||||
this._updatedModel.set({date: this._visDefinitionModel.get('created_at')});
|
||||
},
|
||||
|
||||
_setUpdateFromMapcap: function (mapcaps) {
|
||||
this._updatedModel.set({date: mapcaps[0].created_at});
|
||||
},
|
||||
|
||||
_getMapcaps: function () {
|
||||
var updateFromCreation = this._setUpdateFromCreation.bind(this);
|
||||
var updateFromMapcap = this._setUpdateFromMapcap.bind(this);
|
||||
var url = this._visDefinitionModel.mapcapsURL();
|
||||
var data = {
|
||||
api_key: this._configModel.get('api_key')
|
||||
};
|
||||
|
||||
$.get(url, data)
|
||||
.done(function (data) {
|
||||
if (data.length > 0) {
|
||||
updateFromMapcap(data);
|
||||
} else {
|
||||
updateFromCreation();
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
updateFromCreation();
|
||||
});
|
||||
}
|
||||
});
|
||||
14
lib/assets/javascripts/builder/editor/editor-pane.tpl
Executable file
14
lib/assets/javascripts/builder/editor/editor-pane.tpl
Executable file
@@ -0,0 +1,14 @@
|
||||
<div class="EditorMenu js-editorMenu">
|
||||
<a class="EditorMenu-logo js-editor-logo" href="<%- url %>">
|
||||
<svg width="32px" height="32px" viewBox="762 -58 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g class="imago" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(762.000000, -58.000000)">
|
||||
<circle class="Halo" fill="#FFFFFF" opacity="0.2" cx="16" cy="16" r="16"></circle>
|
||||
<circle class="point" fill="#FFFFFF" cx="16" cy="16" r="5.5"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
<ul class="EditorMenu-navigation js-menu"></ul>
|
||||
</div>
|
||||
<div class="Editor-panelWrapper js-editorPanel">
|
||||
<div class="js-content"></div>
|
||||
</div>
|
||||
113
lib/assets/javascripts/builder/editor/editor-settings-pane.js
Normal file
113
lib/assets/javascripts/builder/editor/editor-settings-pane.js
Normal file
@@ -0,0 +1,113 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var OptionsView = require('./settings/preview/preview-view');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var Header = require('./editor-header.js');
|
||||
var PublishView = require('builder/components/modals/publish/publish-view');
|
||||
var ShareButtonView = require('./layers/share-button-view');
|
||||
var PanelWithOptionsView = require('builder/components/view-options/panel-with-options-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'configModel',
|
||||
'editorModel',
|
||||
'mapDefinitionModel',
|
||||
'mapcapsCollection',
|
||||
'modals',
|
||||
'overlaysCollection',
|
||||
'privacyCollection',
|
||||
'settingsCollection',
|
||||
'userModel',
|
||||
'visDefinitionModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
module: 'editor:editor-settings-pane',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._editorModel.set('edition', false);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
var header = new Header({
|
||||
userModel: this._userModel,
|
||||
editorModel: this._editorModel,
|
||||
configModel: this._configModel,
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
modals: this._modals,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
privacyCollection: this._privacyCollection,
|
||||
clickPrivacyAction: this._share.bind(this),
|
||||
onRemoveMap: this._onRemoveMap.bind(this)
|
||||
});
|
||||
|
||||
header.bind('export-image', function () {
|
||||
this.trigger('export-image', this);
|
||||
}, this);
|
||||
|
||||
this.$el.append(header.render().$el);
|
||||
this.addView(header);
|
||||
|
||||
var view = new PanelWithOptionsView({
|
||||
className: 'Editor-content',
|
||||
editorModel: this._editorModel,
|
||||
infoboxModel: this._infoboxModel,
|
||||
infoboxCollection: this._infoboxCollection,
|
||||
createContentView: function () {
|
||||
return new ScrollView({
|
||||
createContentView: function () {
|
||||
return new OptionsView({
|
||||
mapDefinitionModel: this._mapDefinitionModel,
|
||||
settingsCollection: this._settingsCollection,
|
||||
overlaysCollection: this._overlaysCollection
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
}.bind(this),
|
||||
createActionView: function () {
|
||||
return new ShareButtonView({
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
onClickAction: this._share.bind(this)
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
this.$el.append(view.render().$el);
|
||||
this.addView(view);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_share: function () {
|
||||
var publishView = this._modals.create(function (modalModel) {
|
||||
return new PublishView({
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
modalModel: modalModel,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
privacyCollection: this._privacyCollection,
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel,
|
||||
isOwner: this._isOwner()
|
||||
});
|
||||
}.bind(this), {
|
||||
breadcrumbsEnabled: true
|
||||
});
|
||||
|
||||
this.addView(publishView);
|
||||
},
|
||||
|
||||
_onRemoveMap: function () {
|
||||
window.location = this._userModel.get('base_url');
|
||||
},
|
||||
|
||||
_isOwner: function () {
|
||||
return this._visDefinitionModel
|
||||
.getPermissionModel()
|
||||
.isOwner(this._userModel);
|
||||
}
|
||||
});
|
||||
102
lib/assets/javascripts/builder/editor/editor-settings-view.js
Executable file
102
lib/assets/javascripts/builder/editor/editor-settings-view.js
Executable file
@@ -0,0 +1,102 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var EditorSettingsPane = require('./editor-settings-pane');
|
||||
var ExportImagePane = require('./export-image-pane/export-image-pane');
|
||||
var StackLayoutView = require('builder/components/stack-layout/stack-layout-view');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var REQUIRED_OPTS = [
|
||||
'configModel',
|
||||
'editorModel',
|
||||
'mapcapsCollection',
|
||||
'mapDefinitionModel',
|
||||
'modals',
|
||||
'overlaysCollection',
|
||||
'privacyCollection',
|
||||
'settingsCollection',
|
||||
'visDefinitionModel',
|
||||
'userModel',
|
||||
'stateDefinitionModel',
|
||||
'settingsCollection'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Editor-panel',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
this._editorModel.set('edition', false);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this._generateStackLayoutView();
|
||||
|
||||
var notifierView = Notifier.getView();
|
||||
this.$el.append(notifierView.render().el);
|
||||
this.addView(notifierView);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_generateStackLayoutView: function () {
|
||||
var stackViewCollection = new Backbone.Collection([{
|
||||
createStackView: function (stackLayoutModel, opts) {
|
||||
return this._createEditorSettingsView(stackLayoutModel, opts).bind(this);
|
||||
}.bind(this)
|
||||
}]);
|
||||
|
||||
stackViewCollection.push({
|
||||
createStackView: function (stackLayoutModel, opts) {
|
||||
return this._createExportImageView(stackLayoutModel, opts).bind(this);
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
this._stackLayoutView = new StackLayoutView({
|
||||
className: 'Editor-content',
|
||||
collection: stackViewCollection
|
||||
});
|
||||
this.addView(this._stackLayoutView);
|
||||
this.$el.append(this._stackLayoutView.render().$el);
|
||||
},
|
||||
|
||||
_createEditorSettingsView: function (stackLayoutModel, opts) {
|
||||
var view = new EditorSettingsPane({
|
||||
className: 'Editor-content',
|
||||
configModel: this._configModel,
|
||||
editorModel: this._editorModel,
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
mapDefinitionModel: this._mapDefinitionModel,
|
||||
modals: this._modals,
|
||||
overlaysCollection: this._overlaysCollection,
|
||||
privacyCollection: this._privacyCollection,
|
||||
settingsCollection: this._settingsCollection,
|
||||
userModel: this._userModel,
|
||||
visDefinitionModel: this._visDefinitionModel
|
||||
});
|
||||
|
||||
view.bind('export-image', function () {
|
||||
this._stackLayoutView.model.goToStep(1);
|
||||
}, this);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
_createExportImageView: function (stackLayoutModel, opts) {
|
||||
var view = new ExportImagePane({
|
||||
className: 'Editor-content',
|
||||
canvasClassName: 'CDB-Map',
|
||||
configModel: this._configModel,
|
||||
stackLayoutModel: stackLayoutModel,
|
||||
userModel: this._userModel,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
stateDefinitionModel: this._stateDefinitionModel,
|
||||
mapDefinitionModel: this._mapDefinitionModel,
|
||||
editorModel: this._editorModel,
|
||||
settingsCollection: this._settingsCollection
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
});
|
||||
4
lib/assets/javascripts/builder/editor/editor-tab-pane.tpl
Executable file
4
lib/assets/javascripts/builder/editor/editor-tab-pane.tpl
Executable file
@@ -0,0 +1,4 @@
|
||||
<nav class="CDB-NavMenu js-theme">
|
||||
<ul class="CDB-NavMenu-inner CDB-Text is-semibold CDB-Size-medium js-menu"></ul>
|
||||
</nav>
|
||||
<div class="Editor-content js-content"></div>
|
||||
111
lib/assets/javascripts/builder/editor/editor-view.js
Executable file
111
lib/assets/javascripts/builder/editor/editor-view.js
Executable file
@@ -0,0 +1,111 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var Backbone = require('backbone');
|
||||
var EditorPane = require('./editor-pane');
|
||||
var ExportImagePane = require('./export-image-pane/export-image-pane');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var StackLayoutView = require('builder/components/stack-layout/stack-layout-view');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userActions',
|
||||
'modals',
|
||||
'configModel',
|
||||
'userModel',
|
||||
'editorModel',
|
||||
'pollingModel',
|
||||
'analysisDefinitionNodesCollection',
|
||||
'layerDefinitionsCollection',
|
||||
'privacyCollection',
|
||||
'widgetDefinitionsCollection',
|
||||
'mapcapsCollection',
|
||||
'mapDefinitionModel',
|
||||
'visDefinitionModel',
|
||||
'mapStackLayoutModel',
|
||||
'stateDefinitionModel',
|
||||
'selectedTabItem',
|
||||
'settingsCollection',
|
||||
'routeModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Editor-content',
|
||||
|
||||
events: {
|
||||
'click .js-add': '_addItem'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this._generateStackLayoutView();
|
||||
return this;
|
||||
},
|
||||
|
||||
_generateStackLayoutView: function () {
|
||||
var stackViewCollection = new Backbone.Collection([{
|
||||
createStackView: function (stackLayoutModel, opts) {
|
||||
return this._createEditorView(stackLayoutModel, opts).bind(this);
|
||||
}.bind(this)
|
||||
}]);
|
||||
|
||||
stackViewCollection.push({
|
||||
createStackView: function (stackLayoutModel, opts) {
|
||||
return this._createExportImageView(stackLayoutModel, opts).bind(this);
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
this._stackLayoutView = new StackLayoutView({
|
||||
className: 'Editor-content',
|
||||
collection: stackViewCollection
|
||||
});
|
||||
|
||||
this.addView(this._stackLayoutView);
|
||||
this.$el.append(this._stackLayoutView.render().$el);
|
||||
},
|
||||
|
||||
_createEditorView: function (stackLayoutModel, opts) {
|
||||
var view = new EditorPane({
|
||||
analysisDefinitionNodesCollection: this._analysisDefinitionNodesCollection,
|
||||
configModel: this._configModel,
|
||||
editorModel: this._editorModel,
|
||||
layerDefinitionsCollection: this._layerDefinitionsCollection,
|
||||
mapStackLayoutModel: this._mapStackLayoutModel,
|
||||
mapcapsCollection: this._mapcapsCollection,
|
||||
modals: this._modals,
|
||||
pollingModel: this._pollingModel,
|
||||
privacyCollection: this._privacyCollection,
|
||||
stateDefinitionModel: this._stateDefinitionModel,
|
||||
userActions: this._userActions,
|
||||
userModel: this._userModel,
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
widgetDefinitionsCollection: this._widgetDefinitionsCollection,
|
||||
selectedTabItem: this._selectedTabItem,
|
||||
routeModel: this._routeModel
|
||||
});
|
||||
|
||||
view.bind('export-image', function () {
|
||||
this._stackLayoutView.model.goToStep(1);
|
||||
}, this);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
_createExportImageView: function (stackLayoutModel, opts) {
|
||||
var view = new ExportImagePane({
|
||||
visDefinitionModel: this._visDefinitionModel,
|
||||
canvasClassName: 'CDB-Map',
|
||||
configModel: this._configModel,
|
||||
stackLayoutModel: stackLayoutModel,
|
||||
userModel: this._userModel,
|
||||
mapDefinitionModel: this._mapDefinitionModel,
|
||||
stateDefinitionModel: this._stateDefinitionModel,
|
||||
editorModel: this._editorModel,
|
||||
settingsCollection: this._settingsCollection
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'hasGoogleBasemap'
|
||||
];
|
||||
|
||||
var DIMENSION_LIMIT = 8192;
|
||||
|
||||
var OPTIONS = [{
|
||||
type: 'png',
|
||||
label: '.png',
|
||||
className: 'track-ImageExport track-typePNG'
|
||||
}, {
|
||||
type: 'jpg',
|
||||
label: '.jpg',
|
||||
className: 'track-ImageExport track-typeJPG'
|
||||
}];
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
type: 'none',
|
||||
title: ''
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
checkAndBuildOpts(attrs, REQUIRED_OPTS, this);
|
||||
|
||||
this.schema = this._generateSchema();
|
||||
this._setSchema();
|
||||
this.on('change:format change:width change:height', this._setSchema, this);
|
||||
},
|
||||
|
||||
_setSchema: function () {
|
||||
this.schema = this._generateSchema();
|
||||
},
|
||||
|
||||
_generateSchema: function () {
|
||||
return {
|
||||
width: {
|
||||
type: 'Number',
|
||||
title: _t('editor.export-image.properties.width') + ' (px)',
|
||||
validators: ['required', this._validateDimension.bind(this)],
|
||||
showSlider: false,
|
||||
editorAttrs: {
|
||||
className: 'track-ImageExport track-inputWidth',
|
||||
placeholder: _t('editor.export-image.properties.width')
|
||||
}
|
||||
},
|
||||
height: {
|
||||
type: 'Number',
|
||||
title: _t('editor.export-image.properties.height') + ' (px)',
|
||||
validators: ['required', this._validateDimension.bind(this)],
|
||||
showSlider: false,
|
||||
editorAttrs: {
|
||||
className: 'track-ImageExport track-inputHeight',
|
||||
placeholder: _t('editor.export-image.properties.height')
|
||||
}
|
||||
},
|
||||
format: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.export-image.properties.format'),
|
||||
options: _.map(OPTIONS, function (d) {
|
||||
return {
|
||||
val: d.type,
|
||||
label: d.label,
|
||||
className: d.className
|
||||
};
|
||||
}, this)
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
_validateDimension: function (value, formValues) {
|
||||
var numericValue = +value;
|
||||
var limit = DIMENSION_LIMIT;
|
||||
|
||||
if (_.isNumber(numericValue) && numericValue >= 1 && numericValue <= limit) {
|
||||
return null; // valid dimension
|
||||
}
|
||||
|
||||
return {
|
||||
message: _t('editor.export-image.invalid-dimension', { limit: limit })
|
||||
};
|
||||
},
|
||||
|
||||
setErrors: function (errors) {
|
||||
// we don't want to set these errors as part as attributes
|
||||
// then we manually check if we should trigger the custom event
|
||||
var shouldTrigger = errors !== this._validationErrors;
|
||||
this._validationErrors = errors;
|
||||
shouldTrigger && this.trigger('change:errors');
|
||||
},
|
||||
|
||||
canExport: function () {
|
||||
return _.isUndefined(this._validationErrors);
|
||||
},
|
||||
|
||||
toJSON: function () {
|
||||
return _.extend(
|
||||
this.attributes,
|
||||
{
|
||||
type: this.get('type')
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./export-image-form.tpl');
|
||||
require('builder/components/form-components/index');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var REQUIRED_OPTS = [
|
||||
'formModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this._removeFormView();
|
||||
this._renderForm();
|
||||
this._initBinds();
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderForm: function () {
|
||||
this.clearSubViews();
|
||||
this._removeFormView();
|
||||
|
||||
this._formView = new Backbone.Form({
|
||||
template: template,
|
||||
model: this._formModel
|
||||
});
|
||||
|
||||
this.$('form').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
this.$el.append(this._formView.render().$el);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._formView, 'change', this._updateChanges);
|
||||
this.listenTo(this._formModel, 'change:width change:height', this._updateSchema);
|
||||
},
|
||||
|
||||
_updateSchema: function () {
|
||||
this._formView.fields.width.setValue(this._formModel.get('width'));
|
||||
this._formView.fields.height.setValue(this._formModel.get('height'));
|
||||
this._updateChanges();
|
||||
},
|
||||
|
||||
_updateChanges: function () {
|
||||
var errors = this._formView.commit({validate: true});
|
||||
this._formView.model.setErrors(errors);
|
||||
},
|
||||
|
||||
_removeFormView: function () {
|
||||
this._formView && this._formView.remove();
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this.$('form').off('submit');
|
||||
this._removeFormView();
|
||||
CoreView.prototype.clean.call(this);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="u-flex u-bSpace--xl">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="width,height">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.export-image.properties.size') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.export-image.properties.size-subtitle') %></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-bSpace--xl">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="format">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.export-image.properties.format') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.export-image.properties.format-subtitle') %></p>
|
||||
</div>
|
||||
</div>
|
||||
502
lib/assets/javascripts/builder/editor/export-image-pane/export-image-pane.js
Executable file
502
lib/assets/javascripts/builder/editor/export-image-pane/export-image-pane.js
Executable file
@@ -0,0 +1,502 @@
|
||||
var $ = require('jquery');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var FooterView = require('./footer/footer-view');
|
||||
var ExportImageFormView = require('./export-image-form-view.js');
|
||||
var ExportImageFormModel = require('./export-image-form-model');
|
||||
var ExportImageWidget = require('./export-image-widget');
|
||||
var template = require('./export-image-pane.tpl');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var browser = require('builder/helpers/browser-detect');
|
||||
|
||||
var Infobox = require('builder/components/infobox/infobox-factory');
|
||||
var InfoboxView = require('builder/components/infobox/infobox-view');
|
||||
var InfoboxModel = require('builder/components/infobox/infobox-model');
|
||||
var InfoboxCollection = require('builder/components/infobox/infobox-collection');
|
||||
|
||||
var DEFAULT_EXPORT_FORMAT = 'png';
|
||||
var DEFAULT_EXPORT_WIDTH = 300;
|
||||
var DEFAULT_EXPORT_HEIGHT = 200;
|
||||
|
||||
var CARTO_ATTRIBUTION = '@CARTO';
|
||||
var ATTRIBUTION_WIDTH = 15;
|
||||
var ATTRIBUTION_HEIGHT = 16;
|
||||
|
||||
var NOTIFICATION_ID = 'exportImageNotification';
|
||||
var LOGO_PATH = '/unversioned/images/carto.png';
|
||||
|
||||
var GMAPS_DIMENSION_LIMIT = 640;
|
||||
var GMAPS_DIMENSION_LIMIT_PREMIUM = 2048;
|
||||
|
||||
var MIMETYPES = {
|
||||
'jpg': 'image/jpeg',
|
||||
'png': 'image/png'
|
||||
};
|
||||
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var REQUIRED_OPTS = [
|
||||
'canvasClassName',
|
||||
'configModel',
|
||||
'stackLayoutModel',
|
||||
'userModel',
|
||||
'visDefinitionModel',
|
||||
'stateDefinitionModel',
|
||||
'mapDefinitionModel',
|
||||
'editorModel',
|
||||
'settingsCollection'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Editor-content',
|
||||
|
||||
events: {
|
||||
'click .js-back': '_goStepBack'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._canvasModel = new Backbone.Model();
|
||||
this._$map = $('.' + this._canvasClassName);
|
||||
|
||||
var width = DEFAULT_EXPORT_WIDTH;
|
||||
var height = DEFAULT_EXPORT_HEIGHT;
|
||||
|
||||
var x = Math.ceil(this._$map.width() / 2 - (width / 2));
|
||||
var y = Math.ceil(this._$map.height() / 2 - (height / 2));
|
||||
|
||||
this._exportImageFormModel = new ExportImageFormModel({
|
||||
userModel: this._userModel,
|
||||
hasGoogleBasemap: this._hasGoogleMapsBasemap(),
|
||||
format: DEFAULT_EXPORT_FORMAT,
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
},
|
||||
|
||||
_isLogoActive: function () {
|
||||
var canRemoveLogo = this._userModel.get('actions').remove_logo;
|
||||
var logoSetting = this._settingsCollection.findWhere({
|
||||
setting: 'logo'
|
||||
});
|
||||
|
||||
var isLogoActive = logoSetting && logoSetting.get('enabler') || false;
|
||||
if (!canRemoveLogo || canRemoveLogo && isLogoActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (canRemoveLogo && !isLogoActive) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_addErrorNotification: function (error) {
|
||||
Notifier.addNotification({
|
||||
id: NOTIFICATION_ID,
|
||||
status: 'error',
|
||||
closable: true,
|
||||
button: false,
|
||||
delay: Notifier.DEFAULT_DELAY,
|
||||
info: error + ' ' + _t('editor.maps.export-image.errors.try-again')
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.append(
|
||||
template({
|
||||
mapName: this._getMapName()
|
||||
})
|
||||
);
|
||||
|
||||
this._createWidget();
|
||||
this._createForm();
|
||||
this._createDisclaimer();
|
||||
this._createFooter();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_createForm: function () {
|
||||
this._exportImageFormView = new ExportImageFormView({
|
||||
formModel: this._exportImageFormModel,
|
||||
canvasModel: this._exportImageFormModel
|
||||
});
|
||||
|
||||
this.$('.js-content').append(this._exportImageFormView.render().$el);
|
||||
this.addView(this._exportImageFormView);
|
||||
},
|
||||
|
||||
_createDisclaimer: function () {
|
||||
var infoboxSstates = [{
|
||||
state: 'disclaimer',
|
||||
createContentView: function () {
|
||||
return Infobox.createWithAction({
|
||||
type: 'warning',
|
||||
title: _t('editor.maps.export-image.disclaimer.title'),
|
||||
body: _t('editor.maps.export-image.disclaimer.body')
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
this._infoboxModel = new InfoboxModel({
|
||||
state: 'disclaimer'
|
||||
});
|
||||
|
||||
this._infoboxCollection = new InfoboxCollection(infoboxSstates);
|
||||
|
||||
this._infoboxView = new InfoboxView({
|
||||
infoboxModel: this._infoboxModel,
|
||||
infoboxCollection: this._infoboxCollection
|
||||
});
|
||||
|
||||
this.$('.js-disclaimer').append(this._infoboxView.render().el);
|
||||
this.addView(this._infoboxView);
|
||||
},
|
||||
|
||||
_createFooter: function () {
|
||||
this._footerView = new FooterView({
|
||||
configModel: this._configModel,
|
||||
userModel: this._userModel,
|
||||
formModel: this._exportImageFormModel
|
||||
});
|
||||
|
||||
this.addView(this._footerView);
|
||||
this._footerView.bind('finish', this._exportImage, this);
|
||||
this.$('.js-footer').append(this._footerView.render().el);
|
||||
},
|
||||
|
||||
_createWidget: function () {
|
||||
this._exportImageWidget = new ExportImageWidget({
|
||||
mapViewClass: 'CDB-Map-wrapper',
|
||||
dashboardCanvasClass: 'CDB-Dashboard-canvas',
|
||||
model: this._exportImageFormModel,
|
||||
stateDefinitionModel: this._stateDefinitionModel,
|
||||
mapDefinitionModel: this._mapDefinitionModel
|
||||
});
|
||||
|
||||
this.addView(this._exportImageWidget);
|
||||
this._$map.prepend(this._exportImageWidget.render().$el);
|
||||
},
|
||||
|
||||
_getStaticMapURL: function () {
|
||||
var getStaticImageURL = this._mapDefinitionModel.getStaticImageURLTemplate();
|
||||
var imageMapMetadata = this._mapDefinitionModel.getImageExportMetadata();
|
||||
return getStaticImageURL({
|
||||
zoom: imageMapMetadata.zoom,
|
||||
width: this._exportImageFormModel.get('width'),
|
||||
height: this._exportImageFormModel.get('height'),
|
||||
lat: this._exportImageFormModel.get('lat'),
|
||||
lng: this._exportImageFormModel.get('lng'),
|
||||
format: 'png' // we always use 'png' to allow merging layers
|
||||
});
|
||||
},
|
||||
|
||||
_removeNotification: function () {
|
||||
if (Notifier.getNotification(NOTIFICATION_ID)) {
|
||||
Notifier.removeNotification(NOTIFICATION_ID);
|
||||
}
|
||||
},
|
||||
|
||||
_exportImage: function () {
|
||||
var self = this;
|
||||
|
||||
this._removeNotification();
|
||||
|
||||
var def = $.Deferred();
|
||||
|
||||
var logo = function () {
|
||||
return self._loadLogo();
|
||||
};
|
||||
|
||||
var basemap = function () {
|
||||
if (self._hasGoogleMapsBasemap()) {
|
||||
return self._loadGMapBasemap();
|
||||
}
|
||||
};
|
||||
|
||||
var image = function () {
|
||||
return self._onLoadImage(self._downloadImage.bind(self));
|
||||
};
|
||||
|
||||
var attribution = function () {
|
||||
return self._loadAttribution();
|
||||
};
|
||||
|
||||
def.then(logo)
|
||||
.then(basemap)
|
||||
.then(attribution)
|
||||
.then(image)
|
||||
.fail(this._onFail.bind(this));
|
||||
|
||||
def.resolve();
|
||||
},
|
||||
|
||||
_onFail: function (error) {
|
||||
this._footerView.stopLoader();
|
||||
this._addErrorNotification(error);
|
||||
},
|
||||
|
||||
_getMapName: function () {
|
||||
return this._visDefinitionModel.get('name');
|
||||
},
|
||||
|
||||
_downloadImage: function (canvas) {
|
||||
var browserInfo = browser();
|
||||
var imageFormat = this._exportImageFormModel.get('format');
|
||||
var fileName = this._getMapName() + '.' + imageFormat;
|
||||
|
||||
if (canvas.msToBlob) {
|
||||
var image = canvas.msToBlob();
|
||||
window.navigator.msSaveBlob(
|
||||
new Blob([image], { type: MIMETYPES[imageFormat] }), // eslint-disable-line
|
||||
fileName
|
||||
);
|
||||
} else {
|
||||
var dataUri = canvas.toDataURL(MIMETYPES[imageFormat]);
|
||||
var link = document.createElement('a');
|
||||
document.body.appendChild(link);
|
||||
|
||||
link.download = fileName;
|
||||
link.href = dataUri;
|
||||
|
||||
if (browserInfo.name !== 'Safari') {
|
||||
link.target = '_blank';
|
||||
} else {
|
||||
dataUri.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
|
||||
}
|
||||
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
this._footerView.stopLoader();
|
||||
},
|
||||
|
||||
_hasGoogleMapsBasemap: function () {
|
||||
var imageMapMetadata = this._mapDefinitionModel.getImageExportMetadata();
|
||||
return imageMapMetadata.provider === 'googlemaps';
|
||||
},
|
||||
|
||||
_onLoadImage: function (callback) {
|
||||
var self = this;
|
||||
var url = this._getStaticMapURL();
|
||||
|
||||
var basemap = this._basemap;
|
||||
|
||||
var width = +this._exportImageFormModel.get('width');
|
||||
var height = +this._exportImageFormModel.get('height');
|
||||
|
||||
var image = new Image(); // eslint-disable-line
|
||||
|
||||
image.setAttribute('crossOrigin', 'anonymous');
|
||||
|
||||
image.onload = function () {
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
canvas.width = this.naturalWidth;
|
||||
canvas.height = this.naturalHeight;
|
||||
|
||||
if (basemap) {
|
||||
ctx.drawImage(basemap, 0, 0);
|
||||
}
|
||||
|
||||
ctx.drawImage(this, 0, 0);
|
||||
|
||||
if (self._isLogoActive()) {
|
||||
self._drawLogo(ctx, width, height);
|
||||
}
|
||||
|
||||
self._drawAttribution(ctx, width, height);
|
||||
|
||||
callback(canvas);
|
||||
};
|
||||
|
||||
image.onerror = function (e) {
|
||||
self._onFail(_t('editor.maps.export-image.errors.error-image'));
|
||||
};
|
||||
|
||||
image.src = url;
|
||||
},
|
||||
|
||||
_drawLogo: function (ctx, width, height) {
|
||||
if (this._logo) {
|
||||
var left = (width / 2) - (this._logo.width / 2);
|
||||
var top = height - this._logo.height - 5;
|
||||
|
||||
ctx.drawImage(this._logo, left, top);
|
||||
}
|
||||
},
|
||||
|
||||
_drawAttribution: function (ctx, width, height) {
|
||||
if (this._attribution) {
|
||||
var left = width - this._attribution.width - 3;
|
||||
var top = height - this._attribution.height;
|
||||
|
||||
if (this._hasGoogleMapsBasemap()) {
|
||||
var attributionWidth = this._$map.find('.gm-style-cc').width();
|
||||
|
||||
if (attributionWidth >= width / 2) {
|
||||
top = top - ATTRIBUTION_HEIGHT / 2;
|
||||
}
|
||||
|
||||
top = height - this._attribution.height - ATTRIBUTION_HEIGHT;
|
||||
}
|
||||
|
||||
ctx.drawImage(this._attribution, left, top);
|
||||
}
|
||||
},
|
||||
|
||||
_getDimensionString: function () {
|
||||
var width = this._exportImageFormModel.get('width');
|
||||
var height = this._exportImageFormModel.get('height');
|
||||
|
||||
return width + 'x' + height;
|
||||
},
|
||||
|
||||
_getCenterString: function () {
|
||||
var lat = +this._exportImageFormModel.get('lat').toFixed(6);
|
||||
var lng = +this._exportImageFormModel.get('lng').toFixed(6);
|
||||
|
||||
return lat + ',' + lng;
|
||||
},
|
||||
|
||||
_parseAttribution: function (attribution) {
|
||||
return Utils.stripHTML(
|
||||
attribution
|
||||
.join(' ')
|
||||
.replace(/©/g, '©')
|
||||
);
|
||||
},
|
||||
|
||||
_loadAttribution: function () {
|
||||
var self = this;
|
||||
var visMetadata = this._mapDefinitionModel.getImageExportMetadata();
|
||||
var deferred = $.Deferred();
|
||||
|
||||
var image = new Image(); // eslint-disable-line
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var text = this._hasGoogleMapsBasemap() ? CARTO_ATTRIBUTION : this._parseAttribution(visMetadata.attribution);
|
||||
var dimension = ctx.measureText(text);
|
||||
var width = dimension.width;
|
||||
|
||||
ctx.font = '11px';
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
|
||||
ctx.fillRect(-ATTRIBUTION_WIDTH, 0, width + ATTRIBUTION_WIDTH + 2, ATTRIBUTION_HEIGHT - 3);
|
||||
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.fillText(text, 0, 10);
|
||||
|
||||
image.onload = function () {
|
||||
self._attribution = this;
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
image.onerror = function () {
|
||||
deferred.reject(_t('editor.maps.export-image.errors.error-attribution'));
|
||||
};
|
||||
|
||||
image.width = width;
|
||||
image.height = ATTRIBUTION_HEIGHT - 3;
|
||||
image.src = canvas.toDataURL();
|
||||
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_loadLogo: function () {
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
var logoUrl = this._configModel.get('app_assets_base_url') + LOGO_PATH;
|
||||
|
||||
if (this._isLogoActive()) {
|
||||
this._getImageFromUrl(logoUrl)
|
||||
.then(function (image) {
|
||||
self._logo = image;
|
||||
deferred.resolve();
|
||||
})
|
||||
.fail(function () {
|
||||
deferred.reject(_t('editor.maps.export-image.errors.error-image'));
|
||||
});
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_loadGMapBasemap: function () {
|
||||
var self = this;
|
||||
var apiUrl = self._configModel.get('base_url') + '/api/v1/viz/' + self._visDefinitionModel.get('id') + '/google_maps_static_image';
|
||||
var visMetadata = this._mapDefinitionModel.getImageExportMetadata();
|
||||
var deferred = $.Deferred();
|
||||
|
||||
var onError = function () {
|
||||
deferred.reject(_t('editor.maps.export-image.errors.error-basemap'));
|
||||
};
|
||||
|
||||
var onSuccess = function (data) {
|
||||
var limit = data.url.indexOf('signature') !== -1 ? GMAPS_DIMENSION_LIMIT_PREMIUM : GMAPS_DIMENSION_LIMIT;
|
||||
|
||||
if (self._validGMapDimension(data.url, limit)) {
|
||||
self._getImageFromUrl(data.url)
|
||||
.then(function (image) {
|
||||
self._basemap = image;
|
||||
deferred.resolve();
|
||||
})
|
||||
.fail(onError);
|
||||
} else {
|
||||
deferred.reject(_t('editor.export-image.invalid-dimension', { limit: limit }));
|
||||
}
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: apiUrl,
|
||||
data: {
|
||||
center: this._getCenterString(),
|
||||
size: this._getDimensionString(),
|
||||
zoom: visMetadata.zoom
|
||||
},
|
||||
success: onSuccess,
|
||||
error: onError
|
||||
});
|
||||
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_getImageFromUrl: function (url) {
|
||||
var deferred = $.Deferred();
|
||||
var image = new Image(); // eslint-disable-line
|
||||
|
||||
image.setAttribute('crossOrigin', 'anonymous');
|
||||
|
||||
image.onload = function () {
|
||||
deferred.resolve(this);
|
||||
};
|
||||
|
||||
image.onerror = function () {
|
||||
deferred.reject();
|
||||
};
|
||||
|
||||
image.src = url;
|
||||
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_validGMapDimension: function (url, limit) {
|
||||
var width = this._exportImageFormModel.get('width');
|
||||
var height = this._exportImageFormModel.get('height');
|
||||
|
||||
return width <= limit && height <= limit;
|
||||
},
|
||||
|
||||
_goStepBack: function () {
|
||||
this._stackLayoutModel.prevStep();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
<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.maps.export-image.title') %>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="Editor-HeaderInfoEditor">
|
||||
<div class="Editor-HeaderInfo-inner Editor-HeaderInfo-inner--wide">
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="CDB-Text CDB-Size-huge is-light u-ellipsis"><%- mapName %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Editor-content js-content"></div>
|
||||
<div class="js-disclaimer"></div>
|
||||
<div class="Options-bar Options-bar--right u-flex js-footer"></div>
|
||||
@@ -0,0 +1,217 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./export-image-widget.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var TRACK_CONTEXT_CLASS = 'track-ImageExport';
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'mapViewClass',
|
||||
'dashboardCanvasClass',
|
||||
'stateDefinitionModel',
|
||||
'mapDefinitionModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'ExportImageView',
|
||||
|
||||
events: {
|
||||
'mouseenter .js-canvas': '_onEnter',
|
||||
'mouseleave .js-canvas': '_onLeave'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._$dashBoardCanvas = $('.' + this._dashboardCanvasClass);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.append(template(this.model.attributes));
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._setupCanvas();
|
||||
this._updateHelpers();
|
||||
|
||||
var x = +this.model.get('x');
|
||||
var y = +this.model.get('y');
|
||||
var width = +this.model.get('width');
|
||||
var height = +this.model.get('height');
|
||||
|
||||
this._updateCoordinates(x, y, width, height);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:x change:y change:width change:height', this._updateHelpers);
|
||||
this.listenTo(this._stateDefinitionModel, 'change:json', this._onChangeMap);
|
||||
|
||||
this._resize = _.debounce(this._onResizeWindow.bind(this), 20);
|
||||
$(window).on('resize', this._resize);
|
||||
|
||||
this.listenTo(this._stateDefinitionModel, 'change', this._updateCanvasCoordinates);
|
||||
},
|
||||
|
||||
_onEnter: function () {
|
||||
_.debounce(this.$el.addClass('is-top'), 50);
|
||||
},
|
||||
|
||||
_onLeave: function () {
|
||||
_.debounce(this.$el.removeClass('is-top'), 50);
|
||||
},
|
||||
|
||||
_onResizeWindow: function (e) {
|
||||
if (e.target === window) {
|
||||
var latLngToPixel = this._mapDefinitionModel.latLngToPixel;
|
||||
|
||||
var coordinates = latLngToPixel({
|
||||
lat: this.model.get('lat'),
|
||||
lng: this.model.get('lng')
|
||||
});
|
||||
|
||||
var width = this.model.get('width');
|
||||
var height = this.model.get('height');
|
||||
|
||||
this.model.set({
|
||||
x: coordinates.x - width / 2,
|
||||
y: coordinates.y - height / 2
|
||||
});
|
||||
|
||||
this._updateCanvasCoordinates();
|
||||
this._updateHelpers();
|
||||
}
|
||||
},
|
||||
|
||||
_setupCanvas: function () {
|
||||
this.$('.js-canvas').resizable({
|
||||
resize: this._onResize.bind(this),
|
||||
handles: 'n, e, s, w, ne, se, sw, nw',
|
||||
containment: this._$dashBoardCanvas
|
||||
});
|
||||
|
||||
this.$('.js-canvas').draggable({
|
||||
drag: this._updateCanvasCoordinates.bind(this),
|
||||
stop: this._updateCanvasCoordinates.bind(this),
|
||||
containment: this._$dashBoardCanvas
|
||||
});
|
||||
|
||||
this._setupTracks();
|
||||
|
||||
this.$('.js-canvas').css({
|
||||
top: +this.model.get('x'),
|
||||
left: +this.model.get('y'),
|
||||
width: +this.model.get('width'),
|
||||
height: +this.model.get('height')
|
||||
});
|
||||
},
|
||||
|
||||
_updateHelpers: function () {
|
||||
var x = +this.model.get('x');
|
||||
var y = +this.model.get('y');
|
||||
var width = +this.model.get('width');
|
||||
var height = +this.model.get('height');
|
||||
|
||||
var size = this._mapDefinitionModel.getMapViewSize();
|
||||
var canvasWidth = size && size.x || 0;
|
||||
var canvasHeight = size && size.y || 0;
|
||||
|
||||
var bottom = y + height + 1;
|
||||
var right = x + width + 1;
|
||||
|
||||
this.$('.js-canvas').css({
|
||||
top: y,
|
||||
left: x,
|
||||
height: height,
|
||||
width: width
|
||||
});
|
||||
|
||||
if (x >= 0 && y >= 0) {
|
||||
this.$('.js-helper-north').css({ top: 0, width: right, height: y + 1 });
|
||||
this.$('.js-helper-west').css({ left: 0, top: y + 1, width: x + 1, height: height });
|
||||
this.$('.js-helper-south').css({ top: bottom, left: 0, width: canvasWidth, height: canvasHeight - height - y });
|
||||
this.$('.js-helper-east').css({ left: right, top: 0, width: canvasWidth - width - x - 1, height: bottom });
|
||||
}
|
||||
|
||||
if (right + 1 >= canvasWidth) {
|
||||
this.$('.js-helper-east').css('width', 0);
|
||||
}
|
||||
if (x <= 0) {
|
||||
this.$('.js-helper-west').css('width', 0);
|
||||
}
|
||||
if (bottom + 1 >= canvasHeight) {
|
||||
this.$('.js-helper-south').css('height', 0);
|
||||
}
|
||||
if (y <= 0) {
|
||||
this.$('.js-helper-north').css('height', 0);
|
||||
}
|
||||
},
|
||||
|
||||
_updateCanvasCoordinates: function () {
|
||||
var width = this.$('.js-canvas').width();
|
||||
var height = this.$('.js-canvas').height();
|
||||
var x = this.$('.js-canvas').position().left;
|
||||
var y = this.$('.js-canvas').position().top;
|
||||
|
||||
this._updateCoordinates(x, y, width, height);
|
||||
},
|
||||
|
||||
_onChangeMap: function () {
|
||||
var coordinates = this._calcCenter();
|
||||
|
||||
this.model.set({
|
||||
lat: coordinates.lat,
|
||||
lng: coordinates.lng
|
||||
});
|
||||
},
|
||||
|
||||
_onResize: function (e, ui) {
|
||||
var width = ui.helper.width();
|
||||
var height = ui.helper.height();
|
||||
var x = ui.helper.position().left;
|
||||
var y = ui.helper.position().top;
|
||||
|
||||
this._updateCoordinates(x, y, width, height);
|
||||
},
|
||||
|
||||
_updateCoordinates: function (x, y, width, height) {
|
||||
var coordinates = this._calcCenter();
|
||||
|
||||
this.model.set({
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height,
|
||||
lat: coordinates.lat,
|
||||
lng: coordinates.lng
|
||||
});
|
||||
},
|
||||
|
||||
_calcCenter: function () {
|
||||
var x = this.model.get('x') + this.model.get('width') / 2;
|
||||
var y = this.model.get('y') + this.model.get('height') / 2;
|
||||
var pixelToLatLng = this._mapDefinitionModel.pixelToLatLng;
|
||||
return pixelToLatLng({ x: x, y: y });
|
||||
},
|
||||
|
||||
_setupTracks: function () {
|
||||
this.$('.js-canvas .ui-resizable-nw').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeTopLeft');
|
||||
this.$('.js-canvas .ui-resizable-n').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeTopCenter');
|
||||
this.$('.js-canvas .ui-resizable-ne').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeTopRight');
|
||||
this.$('.js-canvas .ui-resizable-w').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeMiddleLeft');
|
||||
this.$('.js-canvas .ui-resizable-e').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeMiddleCenter');
|
||||
this.$('.js-canvas .ui-resizable-sw').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeBottomLeft');
|
||||
this.$('.js-canvas .ui-resizable-s').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeBottomCenter');
|
||||
this.$('.js-canvas .ui-resizable-se').addClass(TRACK_CONTEXT_CLASS + ' ' + 'track-nodeBottomRight');
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
$(window).off('resize', this._resize);
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="ExportHelper ExportHelper--north js-export-helper js-helper-north"></div>
|
||||
<div class="ExportHelper ExportHelper--west js-export-helper js-helper-west"></div>
|
||||
<div class="ExportHelper ExportHelper--south js-export-helper js-helper-south"></div>
|
||||
<div class="ExportHelper ExportHelper--east js-export-helper js-helper-east"></div>
|
||||
<div class="CanvasExport js-canvas"></div>
|
||||
@@ -0,0 +1,53 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./footer.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var LOADING = 'loading';
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'configModel',
|
||||
'formModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Editor-FooterInfoEditor',
|
||||
|
||||
events: {
|
||||
'click .js-ok': '_finish'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this.model = new Backbone.Model({ state: '' });
|
||||
|
||||
this.listenTo(this.model, 'change:state', this.render);
|
||||
this.listenTo(this._formModel, 'change:errors', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
isLoading: this._isLoading(),
|
||||
isDisabled: !(this._formModel.canExport() && !this._isLoading())
|
||||
}));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
stopLoader: function (e) {
|
||||
this.model.unset('state');
|
||||
},
|
||||
|
||||
_isLoading: function () {
|
||||
return this.model.get('state') === LOADING;
|
||||
},
|
||||
|
||||
_finish: function (e) {
|
||||
this.killEvent(e);
|
||||
this.model.set('state', LOADING);
|
||||
this.trigger('finish', this);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
<button class="CDB-Button CDB-Button--loading CDB-Button--primary js-ok track-ImageExport track-export
|
||||
<% if (isLoading || isDisabled) { %> is-disabled <% } %>
|
||||
<% if (isLoading) { %> is-loading <% } %>
|
||||
"
|
||||
<% if (isDisabled) { %> disabled <% } %>>
|
||||
<div class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase u-flex">
|
||||
<%- _t('editor.maps.export-image.export') %>
|
||||
</div>
|
||||
<div class="CDB-Button-loader CDB-LoaderIcon is-white">
|
||||
<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>
|
||||
</button>
|
||||
44
lib/assets/javascripts/builder/editor/feedback/feedback-button-view.js
Executable file
44
lib/assets/javascripts/builder/editor/feedback/feedback-button-view.js
Executable file
@@ -0,0 +1,44 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./feedback-button.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'a',
|
||||
className: 'EditorMenu-feedback typeform-share button js-feedback',
|
||||
|
||||
attributes: {
|
||||
href: '',
|
||||
target: ''
|
||||
},
|
||||
|
||||
events: {
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.modals) throw new Error('modals is required');
|
||||
this._modals = opts.modals;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.append(template());
|
||||
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: this.el,
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return _t('feedback');
|
||||
}
|
||||
});
|
||||
this.addView(tooltip);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onClick: function () {
|
||||
this.$el.blur();
|
||||
}
|
||||
});
|
||||
8
lib/assets/javascripts/builder/editor/feedback/feedback-button.tpl
Executable file
8
lib/assets/javascripts/builder/editor/feedback/feedback-button.tpl
Executable file
@@ -0,0 +1,8 @@
|
||||
<svg width="18px" height="16px" viewBox="-1 -1 18 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="feedback" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M16,11 L8,11 L5,14 L5,11 L0,11 L0,0 L16,0 L16,11 Z M7.29289322,10.2928932 L7.58578644,10 L8,10 L15,10.0047607 L15.0084229,1 L1,0.993103027 L1,10 L5,10 L6,10 L6,11 L6,11.5374756 L7.29289322,10.2928932 Z" id="Combined-Shape" fill="#FFFFFF "></path>
|
||||
<rect id="Rectangle-25" fill="#FFFFFF " x="4" y="3" width="8" height="1"></rect>
|
||||
<rect id="Rectangle-25" fill="#FFFFFF " x="4" y="5" width="8" height="1"></rect>
|
||||
<rect id="Rectangle-25" fill="#FFFFFF " x="4" y="7" width="5" height="1"></rect>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 753 B |
2
lib/assets/javascripts/builder/editor/feedback/feedback-modal.tpl
Executable file
2
lib/assets/javascripts/builder/editor/feedback/feedback-modal.tpl
Executable file
@@ -0,0 +1,2 @@
|
||||
<div class="typeform-widget" data-url="https://cartohq.typeform.com/to/t7Dk8K" data-text="CARTO Builder" style="width:100%;height:100%;"></div>
|
||||
<script>(function(){var qs,js,q,s,d=document,gi=d.getElementById,ce=d.createElement,gt=d.getElementsByTagName,id='typef_orm',b='https://s3-eu-west-1.amazonaws.com/share.typeform.com/';if(!gi.call(d,id)){js=ce.call(d,'script');js.id=id;js.src=b+'widget.js';q=gt.call(d,'script')[0];q.parentNode.insertBefore(js,q)}})()</script>
|
||||
4
lib/assets/javascripts/builder/editor/inline-editor.tpl
Executable file
4
lib/assets/javascripts/builder/editor/inline-editor.tpl
Executable 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"><%- name %></div>
|
||||
<input type="text" name="text" class="Inline-editor-input Inline-editor-input--small CDB-Text CDB-InputText js-input" value="<%- name %>" readonly>
|
||||
</h2>
|
||||
@@ -0,0 +1,62 @@
|
||||
var $ = require('jquery');
|
||||
var cdb = require('internal-carto.js');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view.js');
|
||||
var NotificationErrorMessageHandler = require('builder/editor/layers/notification-error-message-handler');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
if (!opts.analysisNode) throw new Error('analysisNode is required');
|
||||
if (!opts.element) throw new Error('element is required');
|
||||
if (!opts.triggerSelector) throw new Error('triggerSelector is required');
|
||||
|
||||
this.analysisNode = opts.analysisNode;
|
||||
this.$el = opts.element;
|
||||
this.selector = opts.triggerSelector;
|
||||
|
||||
this.$el.on('mouseover', this.selector, this._showTooltip.bind(this));
|
||||
this.$el.on('mouseout', this.selector, this._destroyTooltip.bind(this));
|
||||
this.$el.on('mouseleave', this.selector, this._destroyTooltip.bind(this));
|
||||
},
|
||||
|
||||
_showTooltip: function (e) {
|
||||
var status = this.analysisNode.get('status');
|
||||
|
||||
if (status === 'failed') {
|
||||
var message = NotificationErrorMessageHandler.extractErrorFromAnalysisNode(this.analysisNode);
|
||||
|
||||
this.tooltip = this._createTooltip({
|
||||
$el: $(e.target),
|
||||
msg: message.message
|
||||
});
|
||||
|
||||
this.tooltip.showTipsy();
|
||||
}
|
||||
},
|
||||
|
||||
_createTooltip: function (opts) {
|
||||
return new TipsyTooltipView({
|
||||
el: opts.$el,
|
||||
title: function () {
|
||||
return opts.msg;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_destroyTooltip: function () {
|
||||
if (this.tooltip) {
|
||||
this.tooltip.hideTipsy();
|
||||
this.tooltip.destroyTipsy();
|
||||
delete this.tooltip;
|
||||
}
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this.$el.off('mouseover', this.selector);
|
||||
this.$el.off('mouseout', this.selector);
|
||||
this.$el.off('mouseleave', this.selector);
|
||||
|
||||
this._destroyTooltip();
|
||||
cdb.core.View.prototype.clean.call(this);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
var _ = require('underscore');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var NotificationErrorMessageHandler = require('builder/editor/layers/notification-error-message-handler');
|
||||
|
||||
var DEFAULT_DELAY = Notifier.DEFAULT_DELAY;
|
||||
var STATUS_READY = 'ready';
|
||||
var STATUS_PENDING = 'pending';
|
||||
var STATUS_RUNNING = 'running';
|
||||
var STATUS_WAITING = 'waiting';
|
||||
var STATUS_FAILED = 'failed';
|
||||
|
||||
var AnalysisNotifications = {
|
||||
|
||||
track: function (analysisNode, layerDefModel) {
|
||||
analysisNode.on('change:status', this._addStatusChangedNotification, this);
|
||||
analysisNode.on('change:error', this._onErrorChanged, this);
|
||||
analysisNode.once('destroy', this._addRemovedNotification, this);
|
||||
this._layerDefModel = layerDefModel;
|
||||
|
||||
if (analysisNode.get('status') && analysisNode.get('status') !== STATUS_READY) {
|
||||
this._addStatusChangedNotification(analysisNode);
|
||||
}
|
||||
},
|
||||
|
||||
_addRemovedNotification: function (analysisNode) {
|
||||
if (analysisNode.get('avoidNotification') === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
var nodeId = analysisNode.get('id');
|
||||
var notificationAttrs = {
|
||||
status: 'success',
|
||||
info: _t('notifications.analysis.removed', {
|
||||
nodeId: nodeId.toUpperCase()
|
||||
}),
|
||||
closable: true,
|
||||
delay: DEFAULT_DELAY
|
||||
};
|
||||
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
|
||||
},
|
||||
|
||||
_addStatusChangedNotification: function (analysisNode) {
|
||||
var status = analysisNode.get('status');
|
||||
if (status === STATUS_WAITING || status === STATUS_PENDING) {
|
||||
this._addAnalysisWaitingNotification(analysisNode);
|
||||
}
|
||||
if (status === STATUS_RUNNING) {
|
||||
this._addAnalysisRunningNotification(analysisNode);
|
||||
}
|
||||
if (status === STATUS_READY) {
|
||||
this._addAnalysisReadyNotification(analysisNode);
|
||||
}
|
||||
if (status === STATUS_FAILED) {
|
||||
this._addAnalysisFailedNotification(analysisNode);
|
||||
}
|
||||
},
|
||||
|
||||
_addAnalysisWaitingNotification: function (analysisNode) {
|
||||
var notificationAttrs = {
|
||||
status: 'loading',
|
||||
info: _t('notifications.analysis.waiting', {
|
||||
nodeId: analysisNode.get('id').toUpperCase()
|
||||
})
|
||||
};
|
||||
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
|
||||
},
|
||||
|
||||
_addAnalysisRunningNotification: function (analysisNode) {
|
||||
var notificationAttrs = {
|
||||
status: 'loading',
|
||||
info: _t('notifications.analysis.running', {
|
||||
nodeId: analysisNode.get('id').toUpperCase()
|
||||
})
|
||||
};
|
||||
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
|
||||
},
|
||||
|
||||
_addAnalysisReadyNotification: function (analysisNode) {
|
||||
var notificationAttrs = {
|
||||
status: 'success',
|
||||
info: _t('notifications.analysis.completed', {
|
||||
nodeId: analysisNode.get('id').toUpperCase()
|
||||
}),
|
||||
closable: true,
|
||||
delay: DEFAULT_DELAY
|
||||
};
|
||||
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
|
||||
},
|
||||
|
||||
_addAnalysisFailedNotification: function (analysisNode) {
|
||||
var message = NotificationErrorMessageHandler.extractErrorFromAnalysisNode(analysisNode, this._layerDefModel);
|
||||
|
||||
var notificationAttrs = {
|
||||
status: message.type,
|
||||
info: message.message,
|
||||
closable: true,
|
||||
autoclosable: false
|
||||
};
|
||||
|
||||
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
|
||||
},
|
||||
|
||||
_addOrUpdateNotification: function (notificationId, notificationAttrs) {
|
||||
var notification = this._getNotification(notificationId);
|
||||
if (notification) {
|
||||
notification.set(notificationAttrs);
|
||||
} else {
|
||||
this._addNotification(notificationId, notificationAttrs);
|
||||
}
|
||||
},
|
||||
|
||||
_addNotification: function (notificationId, notificationAttrs) {
|
||||
notificationAttrs = _.extend({
|
||||
id: notificationId
|
||||
}, notificationAttrs);
|
||||
Notifier.addNotification(notificationAttrs);
|
||||
},
|
||||
|
||||
_getNotification: function (notificationId) {
|
||||
return Notifier.getNotification(notificationId);
|
||||
},
|
||||
|
||||
_onErrorChanged: function (analysisNode, error) {
|
||||
if (error) {
|
||||
this._addAnalysisFailedNotification(analysisNode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = AnalysisNotifications;
|
||||
@@ -0,0 +1,82 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var SourceLayerAnalysisView = require('./source-layer-analysis-view');
|
||||
var DefaultLayerAnalysisView = require('./default-layer-analysis-view');
|
||||
var template = require('./composite-layer-analysis-view.tpl');
|
||||
|
||||
/**
|
||||
* View for an analysis node which have two source nodes as input.
|
||||
* The primary source node is rendered separately, this view renders the own node + the secondary one.
|
||||
* _____________ ___________________
|
||||
* | own node | ------ | secondary node |
|
||||
* |____________| |__________________|
|
||||
* |
|
||||
* ________________
|
||||
* | primary node |
|
||||
* |________________|
|
||||
*
|
||||
* this.model is expected to be a analysis-definition-node-nodel
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'li',
|
||||
className: 'Editor-ListAnalysis-item',
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.analysisDefinitionNodesCollection) throw new Error('analysisDefinitionNodesCollection is required');
|
||||
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
|
||||
|
||||
this._analysisDefinitionNodesCollection = opts.analysisDefinitionNodesCollection;
|
||||
this._layerDefinitionModel = opts.layerDefinitionModel;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template());
|
||||
|
||||
this._renderOwnNode();
|
||||
this._renderSecondaryNode();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderOwnNode: function () {
|
||||
var view = new DefaultLayerAnalysisView({
|
||||
tagName: 'div',
|
||||
model: this.model,
|
||||
analysisNode: this._analysisDefinitionNodesCollection.get(this.model.id),
|
||||
layerDefinitionModel: this._layerDefinitionModel
|
||||
});
|
||||
this.addView(view);
|
||||
this.$('.js-primary-source').append(view.render().el);
|
||||
},
|
||||
|
||||
_renderSecondaryNode: function () {
|
||||
var nodeDefModel = this.model.getSecondarySource();
|
||||
var layerDefModel = this._layerDefinitionModel.collection.findOwnerOfAnalysisNode(nodeDefModel);
|
||||
|
||||
if (nodeDefModel) {
|
||||
var view = nodeDefModel.get('type') === 'source'
|
||||
? new SourceLayerAnalysisView({
|
||||
model: nodeDefModel,
|
||||
analysisNode: this._analysisDefinitionNodesCollection.get(nodeDefModel.id),
|
||||
layerDefinitionModel: this._layerDefinitionModel,
|
||||
showId: !!(layerDefModel && this._isOwnedByOtherLayer(layerDefModel))
|
||||
})
|
||||
: new DefaultLayerAnalysisView({
|
||||
tagName: 'div',
|
||||
model: nodeDefModel,
|
||||
analysisNode: this._analysisDefinitionNodesCollection.get(nodeDefModel.id),
|
||||
layerDefinitionModel: layerDefModel
|
||||
});
|
||||
|
||||
this.addView(view);
|
||||
this.$('.js-secondary-source').append(view.render().el);
|
||||
}
|
||||
},
|
||||
|
||||
_isOwnedByOtherLayer: function (layerDefModel) {
|
||||
return layerDefModel !== this._layerDefinitionModel;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
<ul class="Editor-ListAnalysis-inner">
|
||||
<li class="Editor-ListAnalysis-innerItem js-primary-source"></li>
|
||||
<li class="Editor-ListAnalysis-innerItem js-secondary-source"></li>
|
||||
</ul>
|
||||
@@ -0,0 +1,102 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var nodeIds = require('builder/value-objects/analysis-node-ids');
|
||||
var layerColors = require('builder/data/layer-colors');
|
||||
var template = require('./default-layer-analysis-view.tpl');
|
||||
var Analyses = require('builder/data/analyses');
|
||||
var AnalysisTooltip = require('./analyses-tooltip-error');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
|
||||
/**
|
||||
* View for an analysis node with a single input
|
||||
*
|
||||
* this.model is expected to be a analysis-definition-node-nodel
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'li',
|
||||
className: 'Editor-ListAnalysis-item Editor-ListAnalysis-layer CDB-Text is-semibold CDB-Size-small js-analysis-node',
|
||||
|
||||
events: {
|
||||
'mouseenter': '_onMouseEnter',
|
||||
'mouseleave': '_onMouseLeave'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.analysisNode) throw new Error('analysisNode is required');
|
||||
|
||||
this._analysisNode = opts.analysisNode;
|
||||
this._analysisNode.on('change:status', this.render, this);
|
||||
this.add_related_model(this._analysisNode);
|
||||
|
||||
this._analysisTooltip = new AnalysisTooltip({
|
||||
analysisNode: this._analysisNode,
|
||||
element: this.$el,
|
||||
triggerSelector: '.Editor-ListAnalysis-itemError'
|
||||
});
|
||||
this.addView(this._analysisTooltip);
|
||||
|
||||
this._stateModel = new Backbone.Model({
|
||||
highlighted: false
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._stateModel.on('change:highlighted', this._toggleHover, this);
|
||||
this.add_related_model(this._stateModel);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var status = this._analysisNode.get('status');
|
||||
|
||||
this.$el.html(template({
|
||||
id: this.model.id,
|
||||
bgColor: this._bgColor(),
|
||||
isDone: status === 'ready' || status === 'failed',
|
||||
title: Analyses.title(this.model),
|
||||
hasError: status === 'failed'
|
||||
}));
|
||||
this.$el.toggleClass('has-error', status === 'failed');
|
||||
|
||||
this.el.dataset.analysisNodeId = this.model.id;
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var sourceTooltip = new TipsyTooltipView({
|
||||
el: this.$el,
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return _t('edit-analysis');
|
||||
}
|
||||
});
|
||||
this.addView(sourceTooltip);
|
||||
},
|
||||
|
||||
_bgColor: function () {
|
||||
var letter = nodeIds.letter(this._analysisNode.id);
|
||||
return layerColors.getColorForLetter(letter);
|
||||
},
|
||||
|
||||
_onMouseEnter: function () {
|
||||
this._stateModel.set('highlighted', true);
|
||||
},
|
||||
|
||||
_onMouseLeave: function () {
|
||||
this._stateModel.set('highlighted', false);
|
||||
},
|
||||
|
||||
_toggleHover: function () {
|
||||
var $layer = this.$el.closest('.js-layer');
|
||||
var $title = $layer.find('.js-Editor-ListLayer-titleText');
|
||||
var highlighted = this._stateModel.get('highlighted');
|
||||
|
||||
$layer.toggleClass('is-hover', highlighted);
|
||||
$title.toggleClass('is-hover', highlighted);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="Editor-ListAnalysis-itemInfo u-rSpace--m CDB-Text is-semibold CDB-Size-small u-upperCase" style="background: <%- bgColor %>; color: #fff">
|
||||
<% if (isDone) { %>
|
||||
<span class="CDB-Text u-rSpace">
|
||||
<%- id %>
|
||||
</span>
|
||||
<i class="CDB-IconFont CDB-IconFont-ray CDB-Size-medium"></i>
|
||||
<% } else { %>
|
||||
<div class="CDB-LoaderIcon">
|
||||
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
|
||||
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<p class="Editor-ListAnalysis-title CDB-Text CDB-Size-small u-secondaryTextColor u-ellipsis" title="<%- title %>"><%- title %></p>
|
||||
|
||||
<% if (hasError) { %>
|
||||
<div class="Editor-ListAnalysis-itemError"></div>
|
||||
<% } %>
|
||||
@@ -0,0 +1,79 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var analyses = require('builder/data/analyses');
|
||||
var template = require('./ref-layer-analysis-view.tpl');
|
||||
|
||||
/**
|
||||
* View for an analysis node that belongs to another layer
|
||||
*
|
||||
* this.model is expected to be a analysis-definition-node-nodel
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'li',
|
||||
className: 'Editor-ListAnalysis-item Editor-ListAnalysis-layer CDB-Text is-semibold CDB-Size-small js-analysis-node',
|
||||
|
||||
events: {
|
||||
'mouseenter': '_onMouseEnter',
|
||||
'mouseleave': '_onMouseLeave'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
|
||||
if (!opts.analysisNode) throw new Error('analysisNode is required');
|
||||
|
||||
this._layerDefinitionModel = opts.layerDefinitionModel;
|
||||
this._analysisNode = opts.analysisNode;
|
||||
|
||||
this._stateModel = new Backbone.Model({
|
||||
highlighted: false
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._analysisNode, 'change:status', this.render);
|
||||
this.add_related_model(this._analysisNode);
|
||||
|
||||
this.listenTo(this._layerDefinitionModel, 'change', this.render);
|
||||
this.add_related_model(this._layerDefinitionModel);
|
||||
|
||||
this.listenTo(this._stateModel, 'change:highlighted', this._toggleHover);
|
||||
this.add_related_model(this._stateModel);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var status = this._analysisNode.get('status');
|
||||
|
||||
this.$el.html(template({
|
||||
id: this.model.id,
|
||||
layerName: this._layerDefinitionModel.getName(),
|
||||
bgColor: this.model.getColor(),
|
||||
isDone: status === 'ready' || status === 'failed',
|
||||
title: analyses.title(this.model)
|
||||
}));
|
||||
this.$el.toggleClass('has-error', status === 'failed');
|
||||
|
||||
this.el.dataset.analysisNodeId = this.model.id;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onMouseEnter: function () {
|
||||
this._stateModel.set('highlighted', true);
|
||||
},
|
||||
|
||||
_onMouseLeave: function () {
|
||||
this._stateModel.set('highlighted', false);
|
||||
},
|
||||
|
||||
_toggleHover: function () {
|
||||
var $layer = this.$el.closest('.js-layer');
|
||||
var $title = $layer.find('.js-Editor-ListLayer-titleText');
|
||||
var highlighted = this._stateModel.get('highlighted');
|
||||
|
||||
$layer.toggleClass('is-hover', highlighted);
|
||||
$title.toggleClass('is-hover', highlighted);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="Editor-ListAnalysis-itemInfo u-rSpace--m CDB-Text is-semibold CDB-Size-small u-upperCase" style="background: <%- bgColor %>; color: #fff">
|
||||
<% if (isDone) { %>
|
||||
<span class="CDB-Text u-rSpace">
|
||||
<%- id %>
|
||||
</span>
|
||||
<i class="CDB-IconFont CDB-IconFont-ray CDB-Size-medium"></i>
|
||||
<% } else { %>
|
||||
<div class="CDB-LoaderIcon">
|
||||
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
|
||||
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<p class="Editor-ListAnalysis-title CDB-Text CDB-Size-small u-secondaryTextColor u-ellipsis u-flex" title="<%- title %>">
|
||||
<%- title %> <span class="u-altTextColor u-lSpace u-ellipsis"><%- layerName %></span>
|
||||
</p>
|
||||
@@ -0,0 +1,137 @@
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./source-layer-analysis-view.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var moment = require('moment');
|
||||
|
||||
/**
|
||||
* View for a analysis source (i.e. SQL query).
|
||||
*
|
||||
* this.model is expected to be a analysis-definition-node-model and belong to the given layer-definition-model
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'li',
|
||||
className: 'Editor-ListAnalysis-item Editor-ListAnalysis-layer js-base is-base u-flex u-justifySpace',
|
||||
|
||||
events: {
|
||||
'mouseenter': '_onMouseEnter',
|
||||
'mouseleave': '_onMouseLeave'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
|
||||
if (!opts.analysisNode) throw new Error('analysisNode is required');
|
||||
|
||||
this._layerDefinitionModel = opts.layerDefinitionModel;
|
||||
this._analysisNode = opts.analysisNode;
|
||||
this._tableNodeModel = this._analysisNode.getTableModel();
|
||||
this._stateModel = new Backbone.Model({
|
||||
highlighted: false
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
options: {
|
||||
showId: true
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var isSync = this._tableNodeModel && this._tableNodeModel.isSync();
|
||||
var syncData = {};
|
||||
|
||||
if (isSync) {
|
||||
var syncModel = this._tableNodeModel.getSyncModel();
|
||||
var runAt = syncModel.get('run_at');
|
||||
|
||||
syncData = {
|
||||
ranAt: moment(syncModel.get('ran_at') || new Date()).fromNow(),
|
||||
runAt: moment(runAt).fromNow(),
|
||||
state: syncModel.get('state'),
|
||||
errorCode: syncModel.get('error_code'),
|
||||
errorMessage: syncModel.get('error_message')
|
||||
};
|
||||
|
||||
// Due to the time we need to polling, we have to display to the user
|
||||
// that the sync will be in a moment
|
||||
if (!runAt || (new Date(runAt) <= new Date())) {
|
||||
syncData.runAt = _t('dataset.sync.in-a-moment');
|
||||
}
|
||||
}
|
||||
|
||||
var isCustomQueryApplied = this._analysisNode.isCustomQueryApplied();
|
||||
this.$el.html(template(_.extend({
|
||||
id: this.options.showId ? this.model.id : '',
|
||||
tableName: this.model.get('table_name'),
|
||||
customQueryApplied: isCustomQueryApplied,
|
||||
isSync: isSync,
|
||||
bgColor: this._analysisNode.getColor()
|
||||
}, syncData)));
|
||||
|
||||
var sourceTooltip = new TipsyTooltipView({
|
||||
el: this.$el,
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return _t('data-source');
|
||||
}
|
||||
});
|
||||
this.addView(sourceTooltip);
|
||||
|
||||
if (isSync) {
|
||||
var syncTooltipTitle = (syncData.errorCode || syncData.errorMessage) ? _t('dataset.sync.error-code', { errorCode: syncData.errorCode }) + ':' + syncData.errorMessage : $(this).data('tooltip');
|
||||
var syncTooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-sync'),
|
||||
gravity: 's',
|
||||
offset: 0,
|
||||
title: syncTooltipTitle
|
||||
});
|
||||
this.addView(syncTooltip);
|
||||
}
|
||||
|
||||
if (isCustomQueryApplied) {
|
||||
var sqlTooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-sql'),
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return _t('sql-applied');
|
||||
},
|
||||
mouseEnterAction: function () {
|
||||
sourceTooltip.hideTipsy();
|
||||
},
|
||||
mouseLeaveAction: function () {
|
||||
sourceTooltip.showTipsy();
|
||||
}
|
||||
});
|
||||
this.addView(sqlTooltip);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._stateModel, 'change:highlighted', this._toggleHover);
|
||||
this.listenTo(this._tableNodeModel, 'change:synchronization', this.render);
|
||||
},
|
||||
|
||||
_onMouseEnter: function () {
|
||||
this._stateModel.set('highlighted', true);
|
||||
},
|
||||
|
||||
_onMouseLeave: function () {
|
||||
this._stateModel.set('highlighted', false);
|
||||
},
|
||||
|
||||
_toggleHover: function () {
|
||||
var $layer = this.$el.closest('.js-layer');
|
||||
var $title = $layer.find('.js-Editor-ListLayer-titleText');
|
||||
var highlighted = this._stateModel.get('highlighted');
|
||||
|
||||
$layer.toggleClass('is-hover', highlighted);
|
||||
$title.toggleClass('is-hover', highlighted);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
<p class="Editor-ListAnalysis-itemInfoTitle CDB-Text CDB-Size-small u-ellipsis u-flex" title="<%- tableName %>">
|
||||
<span class="CDB-Text is-semibold CDB-Size-small u-rSpace u-upperCase" style="color: <%- bgColor %>">
|
||||
<%- id %>
|
||||
</span>
|
||||
Source
|
||||
<span class="Editor-ListAnalysis-title u-altTextColor u-lSpace u-ellipsis">
|
||||
<%- tableName %>
|
||||
</span>
|
||||
</p>
|
||||
<% if (isSync) { %>
|
||||
<span class="Editor-ListAnalysis-itemInfoIcon">
|
||||
<div class="u-flex u-alignCenter CDB-Text CDB-Size-small u-altTextColor SyncInfo-message--<%- state %> js-sync" data-tooltip="<% if (errorCode || errorMessage) { %><%- _t('dataset.sync.sync-failed') %><% } else { %><%- ranAt %><% } %>">
|
||||
<i class="CDB-IconFont CDB-IconFont-wifi"></i>
|
||||
</div>
|
||||
</span>
|
||||
<% } %>
|
||||
<% if (customQueryApplied) { %>
|
||||
<span class="Editor-ListAnalysis-itemInfoIcon Tag Tag--outline Tag-outline--dark CDB-Text CDB-Size-small js-sql">
|
||||
SQL
|
||||
</span>
|
||||
<% } %>
|
||||
259
lib/assets/javascripts/builder/editor/layers/basemap-content-view.js
Executable file
259
lib/assets/javascripts/builder/editor/layers/basemap-content-view.js
Executable file
@@ -0,0 +1,259 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var CarouselCollection = require('builder/components/custom-carousel/custom-carousel-collection');
|
||||
var template = require('./basemap-content.tpl');
|
||||
var BasemapHeaderView = require('./basemap-content-views/basemap-header-view');
|
||||
var BasemapModelFactory = require('./basemap-content-views/basemap-model-factory');
|
||||
var BasemapsCollection = require('./basemap-content-views/basemaps-collection');
|
||||
var BasemapInnerView = require('./basemap-content-views/basemap-inner-view');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var Router = require('builder/routes/router');
|
||||
|
||||
var NOTIFICATION_ID = 'basemapNotification';
|
||||
|
||||
var BASEMAP_ICONS = {
|
||||
gmaps: require('./basemap-content-views/basemap-icons/basemap-gmaps.tpl'),
|
||||
carto: require('./basemap-content-views/basemap-icons/basemap-carto.tpl'),
|
||||
stamen: require('./basemap-content-views/basemap-icons/basemap-stamen.tpl'),
|
||||
color: require('./basemap-content-views/basemap-icons/basemap-color.tpl'),
|
||||
mapbox: require('./basemap-content-views/basemap-icons/basemap-mapbox.tpl'),
|
||||
custom: require('./basemap-content-views/basemap-icons/basemap-custom.tpl'),
|
||||
here: require('./basemap-content-views/basemap-icons/basemap-here.tpl'),
|
||||
wms: require('./basemap-content-views/basemap-icons/basemap-wms.tpl'),
|
||||
tilejson: require('./basemap-content-views/basemap-icons/basemap-tilejson.tpl'),
|
||||
nasa: require('./basemap-content-views/basemap-icons/basemap-nasa.tpl')
|
||||
};
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'layerDefinitionsCollection',
|
||||
'stackLayoutModel',
|
||||
'customBaselayersCollection',
|
||||
'basemaps',
|
||||
'modals',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-back': '_onClickBack'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._baseLayer = this._layerDefinitionsCollection.getBaseLayer();
|
||||
|
||||
this.model = new Backbone.Model({
|
||||
disabled: false
|
||||
});
|
||||
|
||||
this._basemapModelFactory = new BasemapModelFactory(this._layerDefinitionsCollection, this._configModel);
|
||||
|
||||
this._initCollections(opts.basemaps);
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:disabled', function () {
|
||||
this.$('.js-basemapContent').toggleClass('is-disabled', this.model.get('disabled'));
|
||||
}, this);
|
||||
|
||||
this._basemapsCollection.bind('change:category', function (mdl) {
|
||||
var userLayersMdl = this._customBaselayersCollection.get(mdl.get('id'));
|
||||
userLayersMdl.set('category', mdl.get('category'));
|
||||
userLayersMdl.save();
|
||||
}, this);
|
||||
this._basemapsCollection.bind('remove', function (mdl) {
|
||||
var userLayersMdl = this._customBaselayersCollection.get(mdl.get('id'));
|
||||
userLayersMdl.destroy();
|
||||
}, this);
|
||||
this._basemapsCollection.bind('add', this._onAddBasemap, this);
|
||||
this.add_related_model(this._basemapsCollection);
|
||||
|
||||
this._layerDefinitionsCollection.bind('changingBaseLayer', this._onChangingBaseLayer, this);
|
||||
this._layerDefinitionsCollection.bind('baseLayerChanged', this._onBaseLayerChanged, this);
|
||||
this._layerDefinitionsCollection.bind('baseLayerFailed', this._onBaseLayerFailed, this);
|
||||
this._layerDefinitionsCollection.bind('change', function () {
|
||||
this._renderHeader();
|
||||
|
||||
this._baseLayer = this._layerDefinitionsCollection.getBaseLayer();
|
||||
this._updateSelectedCategory(this._baseLayer.get('category'));
|
||||
}, this);
|
||||
this.add_related_model(this._layerDefinitionsCollection);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._renderHeader();
|
||||
this._renderContent();
|
||||
},
|
||||
|
||||
_initCollections: function (basemaps) {
|
||||
this._basemapsCollection = new BasemapsCollection();
|
||||
var basemapModel;
|
||||
|
||||
// Basemaps defined in app_config.yml
|
||||
_(basemaps).each(function (categoryBasemaps, category) {
|
||||
_.map(categoryBasemaps, function (basemap) {
|
||||
basemapModel = this._basemapModelFactory.createBasemapModel(category, basemap);
|
||||
this._basemapsCollection.add(basemapModel);
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
// userlayers basemaps
|
||||
this._customBaselayersCollection.each(function (customBaseLayerDefinitionModel) {
|
||||
var category = customBaseLayerDefinitionModel.get('category') || 'Custom';
|
||||
basemapModel = this._basemapModelFactory.createBasemapModel(category, customBaseLayerDefinitionModel.attributes);
|
||||
this._basemapsCollection.add(basemapModel);
|
||||
}, this);
|
||||
|
||||
// Plain color or image basemap
|
||||
basemapModel = this._basemapModelFactory.createBasemapModel('Color', {
|
||||
color: this._baseLayer.get('color') || '',
|
||||
image: this._baseLayer.get('image') || '',
|
||||
selected: this._baseLayer.get('className') === 'plain'
|
||||
});
|
||||
this._basemapsCollection.add(basemapModel);
|
||||
|
||||
this._categoriesCollection = new CarouselCollection(
|
||||
_.map(this._basemapsCollection.getCategories(), function (category) {
|
||||
return {
|
||||
selected: this._getBaseLayerCategory() === category,
|
||||
val: category,
|
||||
label: category,
|
||||
template: function () {
|
||||
var template = this._getTemplateForCategory(category.toLowerCase());
|
||||
return template();
|
||||
}.bind(this)
|
||||
};
|
||||
}, this)
|
||||
);
|
||||
},
|
||||
|
||||
_getTemplateForCategory: function (categoryName) {
|
||||
var template = BASEMAP_ICONS[categoryName.toLowerCase()];
|
||||
if (!template) {
|
||||
template = function () {
|
||||
return categoryName;
|
||||
};
|
||||
}
|
||||
return template;
|
||||
},
|
||||
|
||||
_onChangingBaseLayer: function () {
|
||||
this.model.set('disabled', true);
|
||||
|
||||
if (Notifier.getNotification(NOTIFICATION_ID)) {
|
||||
Notifier.removeNotification(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
this.notification = Notifier.addNotification({
|
||||
id: NOTIFICATION_ID,
|
||||
status: 'loading',
|
||||
info: _t('editor.layers.basemap.saving.loading'),
|
||||
closable: false
|
||||
});
|
||||
},
|
||||
|
||||
_onBaseLayerChanged: function () {
|
||||
this.model.set('disabled', false);
|
||||
|
||||
this.notification.set({
|
||||
status: 'success',
|
||||
info: _t('editor.layers.basemap.saving.success'),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
|
||||
_onBaseLayerFailed: function () {
|
||||
this.model.set('disabled', false);
|
||||
|
||||
this.notification.set({
|
||||
status: 'error',
|
||||
info: _t('editor.layers.basemap.saving.error'),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
|
||||
_updateSelectedCategory: function (category) {
|
||||
var newCategory = this._categoriesCollection.findWhere({ val: category });
|
||||
newCategory && newCategory.set({ selected: true });
|
||||
},
|
||||
|
||||
_onChangeSelectedCategory: function (mdl, isSelected) {
|
||||
if (isSelected) {
|
||||
this._renderContent();
|
||||
}
|
||||
},
|
||||
|
||||
_getBaseLayerCategory: function () {
|
||||
var category = this._basemapsCollection.getDefaultCategory();
|
||||
|
||||
if (this._baseLayer.get('category')) {
|
||||
category = this._baseLayer.get('category');
|
||||
} else if (this._baseLayer.get('type') === 'Plain') {
|
||||
category = 'Color';
|
||||
} else if (this._customBaselayersCollection.hasCustomBaseLayer(this._baseLayer.get('className'))) {
|
||||
category = 'Custom';
|
||||
}
|
||||
|
||||
return category;
|
||||
},
|
||||
|
||||
_renderContent: function () {
|
||||
var self = this;
|
||||
|
||||
if (this._contentView) {
|
||||
this.removeView(this._contentView);
|
||||
this._contentView.clean();
|
||||
}
|
||||
|
||||
this._contentView = new ScrollView({
|
||||
createContentView: function () {
|
||||
return new BasemapInnerView({
|
||||
model: self.model,
|
||||
categoriesCollection: self._categoriesCollection,
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
basemapsCollection: self._basemapsCollection,
|
||||
customBaselayersCollection: self._customBaselayersCollection,
|
||||
modals: self._modals
|
||||
});
|
||||
}
|
||||
});
|
||||
this.addView(this._contentView);
|
||||
this.$('.js-basemapContent').html(this._contentView.render().el);
|
||||
},
|
||||
|
||||
_renderHeader: function () {
|
||||
if (this._headerView) {
|
||||
this.removeView(this._headerView);
|
||||
this._headerView.clean();
|
||||
}
|
||||
|
||||
this._headerView = new BasemapHeaderView({
|
||||
model: this._layerDefinitionsCollection.getBaseLayer(),
|
||||
category: this._getBaseLayerCategory()
|
||||
});
|
||||
this.addView(this._headerView);
|
||||
this.$('.js-basemapHeader').html(this._headerView.render().el);
|
||||
},
|
||||
|
||||
_onAddBasemap: function (mdl) {
|
||||
this._basemapsCollection.updateSelected(mdl.getValue());
|
||||
},
|
||||
|
||||
_onClickBack: function () {
|
||||
Router.goToLayerList();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="js-selector">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.basemap.category.title-label') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m js-highlight"><%- _t('editor.layers.basemap.category.select-category') %></p>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./basemap-categories.tpl');
|
||||
var CarouselFormView = require('builder/components/carousel-form-view');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.categoriesCollection) throw new Error('categoriesCollection is required');
|
||||
|
||||
this._categoriesCollection = opts.categoriesCollection;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template());
|
||||
|
||||
this._renderCarousel();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderCarousel: function () {
|
||||
var view = new CarouselFormView({
|
||||
collection: this._categoriesCollection,
|
||||
template: require('./basemap-carousel.tpl')
|
||||
});
|
||||
this.addView(view);
|
||||
this.$('.js-select').append(view.render().el);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
<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-select"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,68 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var DEBOUNCE_TIME = 650;
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._disabled = opts.disabled;
|
||||
|
||||
this._generateSchema();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._onChange = this._onChange.bind(this);
|
||||
this.bind('change', _.debounce(this._onChange, DEBOUNCE_TIME), this);
|
||||
},
|
||||
|
||||
_onChange: function () {
|
||||
if (this.hasChanged('color')) {
|
||||
var color = this.get('color').color.fixed;
|
||||
this._updatePlainBasemap(color);
|
||||
}
|
||||
},
|
||||
|
||||
_updatePlainBasemap: function (color) {
|
||||
var plainBasemap = this._basemapsCollection.find(function (mdl) {
|
||||
return mdl.get('className') === 'plain';
|
||||
});
|
||||
var isAlreadySelected = plainBasemap.get('selected');
|
||||
|
||||
plainBasemap.set({
|
||||
color: color,
|
||||
image: '',
|
||||
maxZoom: 32
|
||||
});
|
||||
|
||||
if (isAlreadySelected) {
|
||||
this._layerDefinitionsCollection.setBaseLayer(plainBasemap.toJSON());
|
||||
} else {
|
||||
this._basemapsCollection.updateSelected(plainBasemap.getValue());
|
||||
}
|
||||
},
|
||||
|
||||
_generateSchema: function () {
|
||||
this.schema = {
|
||||
color: {
|
||||
type: 'Fill',
|
||||
title: _t('editor.layers.basemap.style.color'),
|
||||
options: [],
|
||||
editorAttrs: {
|
||||
color: {
|
||||
hidePanes: ['value'],
|
||||
disableOpacity: true
|
||||
},
|
||||
disabled: this._disabled
|
||||
},
|
||||
validators: ['required']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./basemap-form.tpl');
|
||||
var BasemapFormModel = require('./basemap-form-model');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._disabled = opts.disabled;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
|
||||
this._generateForms();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_generateForms: function () {
|
||||
if (this._formView) {
|
||||
this._formView.remove();
|
||||
}
|
||||
|
||||
var color = {
|
||||
color: {
|
||||
fixed: '',
|
||||
opacity: 1
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof this.model.get('color') === 'string') {
|
||||
color.color.fixed = this.model.get('color');
|
||||
}
|
||||
|
||||
this._formModel = new BasemapFormModel({
|
||||
color: color,
|
||||
image: this.model.get('image') || ''
|
||||
}, {
|
||||
basemapsCollection: this._basemapsCollection,
|
||||
layerDefinitionsCollection: this._layerDefinitionsCollection,
|
||||
disabled: this._disabled
|
||||
});
|
||||
|
||||
this._formView = new Backbone.Form({
|
||||
model: this._formModel
|
||||
});
|
||||
|
||||
this._formView.bind('change', function () {
|
||||
this.commit();
|
||||
});
|
||||
|
||||
this.$('.js-form').append(this._formView.render().$el);
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
if (this._formView) {
|
||||
this._formView.remove();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="js-selector">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.infowindow.style.title-label') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m js-highlight"><%- _t('editor.layers.infowindow.style.select-style') %></p>
|
||||
</div>
|
||||
|
||||
<div class="js-form"></div>
|
||||
@@ -0,0 +1,36 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./basemap-header.tpl');
|
||||
|
||||
var DEFAULT_NAME = _t('editor.layers.basemap.custom-basemap');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.category) throw new Error('category is required');
|
||||
|
||||
this.category = opts.category;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
title: _t('editor.layers.basemap.title-label'),
|
||||
description: this._getName()
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_getName: function () {
|
||||
var name = this.model.getName();
|
||||
|
||||
if (!name) {
|
||||
name = DEFAULT_NAME;
|
||||
} else {
|
||||
name = this.model.getName().replace(/_/g, '') + ' ' + _t('editor.layers.basemap.by') + ' ' + this.category;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
<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.layers.breadcrumb.basemap-options') %>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="Editor-HeaderInfo u-alignStart">
|
||||
<div class="Editor-HeaderInfo-inner">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace">
|
||||
<h2 class="Editor-HeaderInfo-titleText CDB-Text CDB-Size-huge is-light u-ellipsis"><%- description %></h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAAA4CAYAAAB5YT9uAAAAAXNSR0IArs4c6QAAB51JREFUeAHtW+tv21QUP3HeTtykTdt1a9d1GxNIwBAggRD/O1+RkPgAQhogNq2dlnVdm+Yd23lxfm5v5zqO48dtfTX1SpGvr69Pzv35+ryd+f3Pv+akYGuevKfPHx/Q7s62NO6mpkVjy6JSbS0xTXs8ppFpUrVcpsl0Ru1ej3IZjcp6mfRy6Yp+7qqnWKdULEoFF8vLloo0nUxp0O5QpV5LtOIJ08lqWRrwQ0O7t9lg2hPK5q5Dev0s0V/Ku3kyndJGrS6PoItSoaqTeXziGonXBY8VfmBuQN19QVUTHZWO/eGA9h/cU4mlBV5mDLAfoN6JSgIMJt1yzMt0knO8xrlCMQkJ514tm6X3p2cr6SgHMF697cb6SsbjThictkjfSCZ/8d9r1QrlsznqD4aOslvGj3IAdwd92ttRWzwIMMvlImmaRqPhhaIT4+6jckoun9WokM+7eYzchxjATvW2DGv9yuaGdzj2ebl0YY6Zl5aEHyH1AM4nZ0konzWJNrQfeBizLJsKheUbQikRcd7p0MGDB8vWEmkcdm5Xgjm26k+LxQLZ1tixgf3mKgNwq9Nm02yH6hK8LCwUTsVtgbzBSrPTHRC8O9GGI9PpJn8fBcWYx5OzU+fO/d37tCNRPoIoQL6tBpBbrQ6bgDma2BMa2ZZjaqYOsFGt0nrNoINdOaLBCygUm58L650n4xwgQyaXWGxow4xDMnURUS6W6LzTlbE+XxrGdsPXovCdLGEQMjmbuQAX5FIHGEwUJFgOoBPU7P4w6LK0a3hbII8HLIPRTx3gdq9LTx7uSVugHyGYa2a/T4Oz86Xa3u++OGOd3oAgKkSYNXWAs1qGhMHuXtAqF9Q9N0wfIJdY1vs5IGHuDzMHPNuTsSOHMR/2eOpKTmMl5G0IZMN4T+rReekKB8Q7LvO8qusEOSxa6jt4NltMqHR6fengYsEQEUkD7QI4v2O1orPsHV2zh1MF2LJt2mpcD6y3zjv07+sjP/4TjUHhTNkRuEnbuNsfOOLO/ealCnCn31uwf9+dfqAJg+H2ihIhyzdbrNWH522pgR4/nuBgIIzpbqnLYAStNzluMGID3bbH1O0NnWSnexe4GY7Tt60RGVubcW5deQ/ejHanxwotS9m8tuDUZNLMKk9nYzI5UGJaphO4/vn7b6UrNwAwZu0uI5PsRRu0YfNWDd3RGfDi+iwm6mytCIWa6g7OanmqlPHTWXbp1B8OaadyfadhEYJZ7wLDnI/Z4Nc48yC7IZhjjiyqrVWu+IP1kON4do8fKJT3xnotfUdDLFzn4HWbd4Nor982nZzX4btjMRTraPNikUmW2aAfEGSHQ+F9+DhHRLBmVOiMrRb5jzbmSs7aLVZ4uwRgm+8/UI89r7379xwR0qp2nN0Qk7TU2+BMQAljdwa1C6ANSlUGuxlEshPp+qpe4dfswvlAAP6n7567p0XuQ8TAe5OV3YAZuQpcN5OpmmluRgBq3Vi7AhfXZvOpe0qsPnZSiUOipqSIXY4DU1FMSGUA9kMvw7Ve2IFJG2SwfZlhQH0atH3chmoeuPJhmzIyOCzDcechmyxydJpRdcypRoj6C2GKzeczyrOVgIoev+DUMr7U3sGUWdDSyxayahyiQshhxAyQ2oE8xS+oweSCKQZwx7zzda6edAdzgu7FNWV3MJTeei1YU69anLiOXWiyt4VYROGytLRmGM5lXIM5RRw2ZaFP7l0NUYBqTOfhVBkqjxss6AcdlQUYydAfnn8VxHvoa7AiEEXzC/QAPAEqlJd7R6NyR1wL/WeeicqYaR6+uKh56tjCMsy0m3KVvTz7nSsrg2G2ZS/tYT/Gw45h995EHCLs/ysLMBYwntiJzDRYDTJr0cKC6p6nNMAWhy+n88WMh3sBy/pQXlBokLFpNqUBbtTXY6eOhlxlk2dzLO2mNMBDzm/FbfNZuBL/uPTD3qc0wBnYpjEbzLLbKjYJYlFpgEvF5XW3QYvCNdi81jD+G7CKftjrygKMip/He7th1+E7D2Ii7aYswPCq8BXlTbQOf5V5Wy1dGyZglQZbAElMLDwgwNh8eUimPaLpdO4kV/FFZo5NNxGLCGBByiUlAe4NBlwQ+LFeuM3BclQrbnF4EaWhq4AXaScgtFY1qFS4eBOQXEUu7bD5mgacYP3i8cFKWklRVjIWgUDPj998TW+Oj+kEiUPOCussLpCnE1kOBOPzXIvw9OHDqxQOdu0fL/7hHZon1B0HNcQ6ulz48uWzJze6m5UEGF/aF7n4r2bUrqWQ/AA7ar6lCsdoSwwo6isa9WifaUGZbnIC87NH+37kE48pCXDUVb08OqT6mhEZXPE/qJErFgr07NGu8/U8PgGQVVmkpAwWCw97fLr/yAlvhp3vnQdw8ZXTyNpaqC3zzo16rqyZFnUhItUf9T73fJTNym6fDMAygBFl/zJoCRp3AF8iATHx3+FRpJS8ADHoeAfwJTqwkbv9Ef3y62+RCkuCwD3jmuRPQskFLTLKNVgO+DByyEGiQi16oAmAvnrTpDzXCc/nGcepuQPY9QSEooQrHaX9ze44ihcL+SIZlY8V7nB27kSEB8ntjQa9ePnKM7r8FG457Gg4OG5wxR3/AwJZx54EdH2PAAAAAElFTkSuQmCC" />
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="23px" height="25px" viewBox="1383 664 23 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="color" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(1383.000000, 664.000000)">
|
||||
<path d="M19.04725,12.691 C19.12525,12.504 19.08225,12.289 18.93925,12.146 L9.93925,3.146 C9.84125,3.049 9.71325,3 9.58525,3 L9.58525,2.5 C9.58525,1.122 8.46325,0 7.08525,0 C5.70725,0 4.58525,1.122 4.58525,2.5 L4.58525,7.793 L0.73125,11.647 C-0.24375,12.622 -0.24375,14.208 0.73125,15.183 L5.40325,19.854 C5.87525,20.326 6.50325,20.586 7.17125,20.586 C7.83925,20.586 8.46625,20.326 8.93925,19.854 L15.79225,13 L18.58525,13 C18.78725,13 18.97025,12.878 19.04725,12.691 L19.04725,12.691 Z M5.58525,2.5 C5.58525,1.673 6.25825,1 7.08525,1 C7.91225,1 8.58525,1.673 8.58525,2.5 L8.58525,3.793 L5.58525,6.793 L5.58525,2.5 L5.58525,2.5 Z M15.58525,12 C15.45225,12 15.32525,12.053 15.23125,12.146 L8.23125,19.146 C7.66525,19.712 6.67625,19.712 6.11025,19.146 L1.43825,14.475 C0.85325,13.89 0.85325,12.938 1.43825,12.353 L5.38925,8.401 C5.42025,8.377 5.44725,8.351 5.47125,8.32 L8.58525,5.207 L8.58525,10.5 C8.58525,10.776 8.80925,11 9.08525,11 C9.36125,11 9.58525,10.776 9.58525,10.5 L9.58525,4.207 L17.37825,12 L15.58525,12 L15.58525,12 Z" id="Shape" fill="#AAAAAA"></path>
|
||||
<path d="M20.01225,15.241 C19.83125,14.942 19.33825,14.942 19.15825,15.241 C18.89425,15.674 16.58525,19.522 16.58525,21 C16.58525,22.654 17.93125,24 19.58525,24 C21.23925,24 22.58525,22.654 22.58525,21 C22.58525,19.522 20.27625,15.674 20.01225,15.241 L20.01225,15.241 Z M19.58525,23 C18.48225,23 17.58525,22.103 17.58525,21 C17.58525,20.225 18.68125,18.061 19.58525,16.485 C20.48925,18.061 21.58525,20.225 21.58525,21 C21.58525,22.103 20.68825,23 19.58525,23 L19.58525,23 Z" id="Shape" fill="#AAAAAA"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="22px" height="24px" viewBox="966 769 22 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M973.5,791.5 L966.5,786.5 L966.5,770.5 L973.5,775.5 L980.5,770.5 L987.5,775.5 L987.5,791.5 L980.5,786.5 L973.5,791.5 Z M973.5,775.5 L973.5,791.5 L973.5,775.5 Z M980.5,786.5 L980.5,770.531 L980.5,786.5 Z" id="Shape" stroke="#AAAAAA" stroke-width="1" fill="none"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
@@ -0,0 +1 @@
|
||||
<p class="CDB-Text CDB-Size-small u-upperCase u-altTextColor">Google maps</p>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<p class="CDB-Text CDB-Size-small u-upperCase u-altTextColor">Mapbox</p>
|
||||
@@ -0,0 +1 @@
|
||||
<p class="CDB-Text CDB-Size-small u-upperCase u-altTextColor">nasa</p>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<p class="CDB-Text CDB-Size-small u-upperCase u-altTextColor">tilejson</p>
|
||||
@@ -0,0 +1 @@
|
||||
<p class="CDB-Text CDB-Size-small u-upperCase u-altTextColor">wms/wmts</p>
|
||||
@@ -0,0 +1,85 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var BasemapCategoriesView = require('./basemap-categories-view');
|
||||
var BasemapSelectView = require('./basemap-select-view');
|
||||
var template = require('./basemap-inner.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Editor-inner',
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.categoriesCollection) throw new Error('categoriesCollection is required');
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
if (!opts.modals) throw new Error('modals is required');
|
||||
|
||||
this._categoriesCollection = opts.categoriesCollection;
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
this._modals = opts.modals;
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change:disabled', function () {
|
||||
this._renderSelect();
|
||||
}, this);
|
||||
|
||||
this._basemapsCollection.bind('add remove', function () {
|
||||
this._renderSelect();
|
||||
}, this);
|
||||
this.add_related_model(this._basemapsCollection);
|
||||
|
||||
this._categoriesCollection.bind('change:selected', this._renderSelect, this);
|
||||
this.add_related_model(this._categoriesCollection);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._renderCategories();
|
||||
this._renderSelect();
|
||||
},
|
||||
|
||||
_renderCategories: function () {
|
||||
if (this._categoriesView) {
|
||||
this.removeView(this._categoriesView);
|
||||
this._categoriesView.clean();
|
||||
}
|
||||
|
||||
this._categoriesView = new BasemapCategoriesView({
|
||||
categoriesCollection: this._categoriesCollection
|
||||
});
|
||||
|
||||
this.addView(this._categoriesView);
|
||||
this.$('.js-basemapCategory').html(this._categoriesView.render().el);
|
||||
},
|
||||
|
||||
_renderSelect: function () {
|
||||
if (this._selectView) {
|
||||
this.removeView(this._selectView);
|
||||
this._selectView.clean();
|
||||
}
|
||||
|
||||
this._selectView = new BasemapSelectView({
|
||||
layerDefinitionsCollection: this._layerDefinitionsCollection,
|
||||
basemapsCollection: this._basemapsCollection,
|
||||
customBaselayersCollection: this._customBaselayersCollection,
|
||||
selectedCategoryVal: this._categoriesCollection.getSelectedValue(),
|
||||
modals: this._modals,
|
||||
disabled: this.model.get('disabled')
|
||||
});
|
||||
this.addView(this._selectView);
|
||||
this.$('.js-basemapSelect').html(this._selectView.render().el);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
<div class="js-basemapCategory"></div>
|
||||
<div class="js-basemapSelect"></div>
|
||||
@@ -0,0 +1,188 @@
|
||||
var _ = require('underscore');
|
||||
var basemapProvidersAndCategories = require('builder/data/basemap-providers-and-categories');
|
||||
var mosaicThumbnail = require('builder/components/mosaic/mosaic-thumbnail.tpl');
|
||||
var BasemapModel = require('./basemap-model');
|
||||
|
||||
var DEFAULT_SUBDOMAIN = 'a';
|
||||
var DEFAULT_X_POSITION = 30;
|
||||
var DEFAULT_Y_POSISTION = 24;
|
||||
var DEFAULT_ZOOM = 6;
|
||||
|
||||
// 'CartoDB', 'Stamen', 'Here'
|
||||
|
||||
var createLeafletBasemapModel = function (category, basemap, baseLayerDefinition, assetsBaseURL) {
|
||||
var value;
|
||||
var currentValue;
|
||||
var label;
|
||||
var imageURL;
|
||||
|
||||
var type = basemapProvidersAndCategories.getLayerType(category);
|
||||
value = basemap.className;
|
||||
currentValue = baseLayerDefinition.get('className');
|
||||
label = basemap.name;
|
||||
imageURL = lowerXYZ(basemap.urlTemplate, basemap.subdomains);
|
||||
|
||||
_.extend(basemap, {
|
||||
type: type,
|
||||
name: basemap.name,
|
||||
category: category,
|
||||
selected: currentValue === value,
|
||||
val: value,
|
||||
label: label,
|
||||
template: function () {
|
||||
return mosaicThumbnail({
|
||||
imgURL: imageURL
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return new BasemapModel(basemap);
|
||||
};
|
||||
|
||||
var lowerXYZ = function (urlTemplate, subdomains) {
|
||||
return urlTemplate
|
||||
.replace('{s}', getSubdomain(subdomains))
|
||||
.replace('{z}', DEFAULT_ZOOM)
|
||||
.replace('{x}', DEFAULT_X_POSITION)
|
||||
.replace('{y}', DEFAULT_Y_POSISTION);
|
||||
};
|
||||
|
||||
var getSubdomain = function (subdomains) {
|
||||
return (subdomains && subdomains.length) ? subdomains[0] : DEFAULT_SUBDOMAIN;
|
||||
};
|
||||
|
||||
// Google Maps
|
||||
|
||||
var createGoogleMapsBasemapModel = function (category, basemapAttrs, baseLayerDefinition, assetsBaseURL) {
|
||||
var value;
|
||||
var currentValue;
|
||||
var label;
|
||||
var imageURL;
|
||||
|
||||
var type = basemapProvidersAndCategories.getLayerType(category);
|
||||
var generateValue = function (baseType, baseName) {
|
||||
var value = baseType;
|
||||
if (baseName) {
|
||||
value = value + '_' + baseName;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
value = generateValue(basemapAttrs.baseType, basemapAttrs.baseName);
|
||||
currentValue = generateValue(baseLayerDefinition.get('baseType'), baseLayerDefinition.get('baseName'));
|
||||
label = basemapAttrs.name.replace('GMaps ', '');
|
||||
imageURL = [
|
||||
assetsBaseURL,
|
||||
'unversioned/images/google-maps-basemap-icons',
|
||||
value + '.jpg'
|
||||
].join('/');
|
||||
|
||||
_.extend(basemapAttrs, {
|
||||
type: type,
|
||||
name: basemapAttrs.name,
|
||||
category: category,
|
||||
selected: currentValue === value,
|
||||
val: value,
|
||||
label: label,
|
||||
template: function () {
|
||||
return mosaicThumbnail({
|
||||
imgURL: imageURL
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return new BasemapModel(basemapAttrs);
|
||||
};
|
||||
|
||||
// Custom
|
||||
|
||||
var createCustomBasemapModel = function (category, customBaseLayerAttrs, baseLayerDefinition, configModel) {
|
||||
var name = customBaseLayerAttrs.name ? customBaseLayerAttrs.name : 'Custom basemap ' + customBaseLayerAttrs.order;
|
||||
var className = customBaseLayerAttrs.className;
|
||||
var urlTemplate = customBaseLayerAttrs.urlTemplate;
|
||||
|
||||
return new BasemapModel({
|
||||
id: customBaseLayerAttrs.id,
|
||||
urlTemplate: urlTemplate,
|
||||
minZoom: customBaseLayerAttrs.minZoom || 0,
|
||||
maxZoom: customBaseLayerAttrs.maxZoom || 21,
|
||||
name: name,
|
||||
className: className,
|
||||
attribution: customBaseLayerAttrs.attribution,
|
||||
category: category || 'Custom',
|
||||
tms: customBaseLayerAttrs.tms,
|
||||
selected: baseLayerDefinition.get('className') === className,
|
||||
val: className,
|
||||
label: name,
|
||||
template: function (imgURL) {
|
||||
return mosaicThumbnail({
|
||||
imgURL: imgURL
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Color
|
||||
|
||||
var createColorBasemapModel = function (category, basemapAttrs, baseLayerDefinition, configModel) {
|
||||
return new BasemapModel({
|
||||
default: false,
|
||||
color: basemapAttrs.color || '',
|
||||
image: basemapAttrs.image || '',
|
||||
maxZoom: 32,
|
||||
className: 'plain',
|
||||
category: 'Color',
|
||||
type: 'Plain',
|
||||
selected: baseLayerDefinition.get('className') === 'plain',
|
||||
val: 'plain',
|
||||
label: 'plain',
|
||||
template: function () {
|
||||
return 'plain';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Factory
|
||||
|
||||
var CATEGORY_CARTODB = 'CARTO';
|
||||
var CATEGORY_STAMEN = 'Stamen';
|
||||
var CATEGORY_HERE = 'Here';
|
||||
var CATEGORY_GMAPS = 'GMaps';
|
||||
var CATEGORY_CUSTOM = 'Custom';
|
||||
var CATEGORY_NASA = 'NASA';
|
||||
var CATEGORY_TILEJSON = 'TileJSON';
|
||||
var CATEGORY_MAPBOX = 'Mapbox';
|
||||
var CATEGORY_WMS = 'WMS';
|
||||
var CATEGORY_COLOR = 'Color';
|
||||
|
||||
var CREATE_METHODS = {};
|
||||
CREATE_METHODS[CATEGORY_CARTODB] = createLeafletBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_STAMEN] = createLeafletBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_HERE] = createLeafletBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_GMAPS] = createGoogleMapsBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_CUSTOM] = createCustomBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_NASA] = createCustomBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_TILEJSON] = createCustomBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_MAPBOX] = createCustomBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_WMS] = createCustomBasemapModel;
|
||||
CREATE_METHODS[CATEGORY_COLOR] = createColorBasemapModel;
|
||||
|
||||
var BasemapModelFactory = function (layerDefinitionsCollection, configModel) {
|
||||
if (!layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!configModel) throw new Error('configModel is required');
|
||||
|
||||
this._layerDefinitionsCollection = layerDefinitionsCollection;
|
||||
this._configModel = configModel;
|
||||
};
|
||||
|
||||
BasemapModelFactory.prototype.createBasemapModel = function (basemapCategory, basemapAttrs) {
|
||||
var baseLayer = this._layerDefinitionsCollection.getBaseLayer();
|
||||
var assetsBaseURL = this._configModel.get('app_assets_base_url');
|
||||
|
||||
var createMethod = CREATE_METHODS[basemapCategory];
|
||||
if (!createMethod) {
|
||||
throw new Error("Can't create basemapModel for basemap of type: " + basemapCategory);
|
||||
}
|
||||
return createMethod(basemapCategory, basemapAttrs, baseLayer, assetsBaseURL);
|
||||
};
|
||||
|
||||
module.exports = BasemapModelFactory;
|
||||
@@ -0,0 +1,28 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
default: false,
|
||||
minZoom: 0,
|
||||
maxZoom: 21,
|
||||
name: '',
|
||||
category: 'Custom',
|
||||
type: 'Tiled',
|
||||
selected: false,
|
||||
val: '',
|
||||
label: '',
|
||||
template: function () {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
getName: function () {
|
||||
return this.get('label') || this.getValue();
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
return this.get('val');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('builder/components/mosaic/mosaic-item.tpl');
|
||||
var AddBasemapView = require('builder/components/modals/add-basemap/add-basemap-view');
|
||||
var AddBasemapModel = require('builder/components/modals/add-basemap/add-basemap-model');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Mosaic-item Mosaic-item--dashed',
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
events: {
|
||||
'mouseenter': '_onMouseEnter',
|
||||
'mouseleave': '_onMouseLeave',
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.modals) throw new Error('modals is required');
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
if (!opts.mosaicModel) throw new Error('mosaicModel is required');
|
||||
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
this._modals = opts.modals;
|
||||
this._currentTab = opts.currentTab;
|
||||
this._mosaicModel = opts.mosaicModel;
|
||||
|
||||
this.add_related_model(this._mosaicModel);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
name: this.model.getName(),
|
||||
template: this.model.get('template')()
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
_onMouseEnter: function () {
|
||||
this._mosaicModel.set('highlightedAdd', true);
|
||||
},
|
||||
|
||||
_onMouseLeave: function () {
|
||||
this._mosaicModel.set('highlightedAdd', false);
|
||||
},
|
||||
|
||||
_onClick: function () {
|
||||
var self = this;
|
||||
|
||||
var modal = this._modals.create(function (modalModel) {
|
||||
var addBasemapModel = new AddBasemapModel({}, {
|
||||
layerDefinitionsCollection: self._layerDefinitionsCollection,
|
||||
basemapsCollection: self._basemapsCollection,
|
||||
customBaselayersCollection: self._customBaselayersCollection,
|
||||
currentTab: self._currentTab
|
||||
});
|
||||
|
||||
return new AddBasemapView({
|
||||
modalModel: modalModel,
|
||||
createModel: addBasemapModel
|
||||
});
|
||||
}, {
|
||||
breadcrumbsEnabled: true
|
||||
});
|
||||
modal.show();
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
var BasemapMosaicView = require('./basemap-mosaic-view');
|
||||
var BasemapMosaicModel = require('./basemap-mosaic-model');
|
||||
var MosaicFormView = require('builder/components/mosaic-form-view');
|
||||
|
||||
/**
|
||||
* Mosaic form view
|
||||
*/
|
||||
|
||||
module.exports = MosaicFormView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.collection) throw new Error('Mosaic collection is required');
|
||||
if (!opts.template) throw new Error('template is required');
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
if (!opts.modals) throw new Error('modals is required');
|
||||
|
||||
this.template = opts.template;
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
this._modals = opts.modals;
|
||||
this._disabled = opts.disabled;
|
||||
this._currentTab = opts.currentTab;
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this.mosaic = new BasemapMosaicView({
|
||||
model: new BasemapMosaicModel(),
|
||||
collection: this.collection,
|
||||
layerDefinitionsCollection: this._layerDefinitionsCollection,
|
||||
basemapsCollection: this._basemapsCollection,
|
||||
customBaselayersCollection: this._customBaselayersCollection,
|
||||
modals: this._modals,
|
||||
disabled: this._disabled,
|
||||
currentTab: this._currentTab
|
||||
});
|
||||
this.mosaic.model.bind('change:highlightedAdd', this._onChangeHighlighted, this);
|
||||
|
||||
if (!this.$('.js-selector').length) throw new Error('HTML element with js-selector class is required');
|
||||
|
||||
this.$('.js-selector').append(this.mosaic.render().el);
|
||||
this.addView(this.mosaic);
|
||||
},
|
||||
|
||||
_onChangeHighlighted: function () {
|
||||
var item = this.collection.getHighlighted() || this.mosaic.model.getHighlighted() || this.collection.getSelected();
|
||||
if (item) {
|
||||
var $el = this.$('.js-highlight');
|
||||
if ($el) {
|
||||
$el.text(item.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/* global Image */
|
||||
|
||||
var template = require('builder/components/mosaic/mosaic-item.tpl');
|
||||
var MosaicItemView = require('builder/components/mosaic/mosaic-item-view');
|
||||
var BasemapMosaicRemoveView = require('./basemap-mosaic-remove-view');
|
||||
|
||||
var DEFAULT_SUBDOMAIN = 'a';
|
||||
var DEFAULT_X_POSITION = 30;
|
||||
var DEFAULT_Y_POSISTION = 24;
|
||||
var DEFAULT_ZOOM = 6;
|
||||
var DEFAULT_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAZ9JREFUWAntWUtugzAUNKbAJl1EYtcz9Ba5UE6SC+UWPUN3SNlkw78zVhJRCeIXbCup6ic5gP08Ho/x40ESBauq6iNJkgPKDpdb1j3RTuM4HlH2ZVl+JySXpukXygaksiViRVEsNd3qu65Tfd/frpdO8jxXEGO2GcTUMAxtXddnYH1qKqe1fof3IrlZpECVJA6xMgiyMdzws0N5CzTealiIlpGbBsKz77nZSYAc67ck+NIWCbouj1cFL/eNK6df/UW7FztKMT7ZTOJDDMQ5RUyJGYK2IMyBm6ax4nFQGxZBEIStWFcH2TSu3pZjiCX2StDCf1VzJLhKtkmnqOBEjFWn/0tBaaB+REoTqJkJ2wxJpM3FtPvGMgQlabrkCUEcCcF7Kf9UBT5xvN6DIZbYK8Hp7H2dR4KuSkYFo4KPKBAiYTWBmoHznvEdQpKm82ljw+I4DObElJghaJu59AWHA9qw6CMlR9+4i6mCi0UFXdRj3z+hIL8Ju040VP+TBrkjtn0bagQH3JbcSHB/+WDNCgc8r11bZOdncjPfWV/5b4gfOaaYLUG/QVoAAAAASUVORK5CYII=';
|
||||
var DEFAULT_NAME = _t('editor.layers.basemap.custom-basemap');
|
||||
|
||||
module.exports = MosaicItemView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._disabled = opts.disabled;
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
name: this._getName(),
|
||||
template: this.model.get('template')(this._getImageURL())
|
||||
})
|
||||
);
|
||||
this.$el.addClass('js-' + this.model.getValue());
|
||||
this.$el.toggleClass('is-selected', !!this.model.get('selected'));
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var basemapMosaicRemoveView = new BasemapMosaicRemoveView({
|
||||
model: this.model,
|
||||
basemapsCollection: this._basemapsCollection
|
||||
});
|
||||
this.addView(basemapMosaicRemoveView);
|
||||
this.$el.append(basemapMosaicRemoveView.render().el);
|
||||
},
|
||||
|
||||
_getImageURL: function () {
|
||||
var self = this;
|
||||
var url = this._lowerXYZ();
|
||||
|
||||
var image = new Image();
|
||||
image.onerror = function () {
|
||||
self.$('.js-thumbnailImg').attr('src', DEFAULT_IMG);
|
||||
};
|
||||
image.src = url;
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
_getSubdomain: function () {
|
||||
var subdomains = this.model.get('subdomains'); // eg: 'abcd' or '1234'
|
||||
|
||||
return (subdomains && subdomains.length) ? subdomains[0] : DEFAULT_SUBDOMAIN;
|
||||
},
|
||||
|
||||
_lowerXYZ: function () {
|
||||
return this.model.get('urlTemplate')
|
||||
.replace('{s}', this._getSubdomain())
|
||||
.replace('{z}', DEFAULT_ZOOM)
|
||||
.replace('{x}', DEFAULT_X_POSITION)
|
||||
.replace('{y}', DEFAULT_Y_POSISTION);
|
||||
},
|
||||
|
||||
_getName: function () {
|
||||
var name = this.model.getName();
|
||||
|
||||
if (!name) {
|
||||
name = this.model.get('order') ? DEFAULT_NAME + ' ' + this.model.get('order') : DEFAULT_NAME;
|
||||
} else {
|
||||
name.replace(/_/g, '');
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
/*
|
||||
* List item model
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
highlightedAdd: false
|
||||
},
|
||||
|
||||
getHighlighted: function () {
|
||||
return this.get('highlightedAdd') && this;
|
||||
},
|
||||
|
||||
getName: function () {
|
||||
return 'Add';
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./basemap-mosaic-remove.tpl');
|
||||
var CustomListCollection = require('builder/components/custom-list/custom-list-collection');
|
||||
var ContextMenuView = require('builder/components/context-menu/context-menu-view');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'span',
|
||||
className: 'Mosaic-remove CDB-Shape-threePoints is-white is-small js-Mosaic-remove',
|
||||
|
||||
events: {
|
||||
'click': '_onToggleContextMenuClicked'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template());
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_hasContextMenu: function () {
|
||||
return this._menuView;
|
||||
},
|
||||
|
||||
_hideContextMenu: function () {
|
||||
this.removeView(this._menuView);
|
||||
this._menuView.clean();
|
||||
delete this._menuView;
|
||||
},
|
||||
|
||||
_showContextMenu: function (position) {
|
||||
this._menuItems = new CustomListCollection([{
|
||||
label: _t('editor.layers.basemap.remove-baselayer'),
|
||||
val: 'remove-baselayer',
|
||||
destructive: true
|
||||
}]);
|
||||
|
||||
var triggerElementID = 'context-menu-trigger-' + this.model.cid;
|
||||
this.$('.js-Mosaic-remove').attr('id', triggerElementID);
|
||||
this._menuView = new ContextMenuView({
|
||||
collection: this._menuItems,
|
||||
triggerElementID: triggerElementID,
|
||||
position: position
|
||||
});
|
||||
|
||||
this._menuItems.bind('change:selected', function (menuItem) {
|
||||
if (menuItem.get('val') === 'remove-baselayer') {
|
||||
this._deleteBaselayer();
|
||||
}
|
||||
}, this);
|
||||
this.add_related_model(this._menuItems);
|
||||
|
||||
this._menuView.model.bind('change:visible', function (model, isContextMenuVisible) {
|
||||
if (this._hasContextMenu() && !isContextMenuVisible) {
|
||||
this._hideContextMenu();
|
||||
}
|
||||
}, this);
|
||||
|
||||
this._menuView.show();
|
||||
this.addView(this._menuView);
|
||||
},
|
||||
|
||||
_deleteBaselayer: function () {
|
||||
this._basemapsCollection.remove(this.model);
|
||||
},
|
||||
|
||||
_onToggleContextMenuClicked: function (e) {
|
||||
this.killEvent(e);
|
||||
|
||||
if (this._hasContextMenu()) {
|
||||
this._hideContextMenu();
|
||||
} else {
|
||||
this._showContextMenu({
|
||||
x: e.pageX + 215, // CustomList--small width
|
||||
y: e.pageY
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
<div class="CDB-Shape-threePointsItem"></div>
|
||||
@@ -0,0 +1,61 @@
|
||||
var template = require('builder/components/mosaic/mosaic.tpl');
|
||||
var MosaicView = require('builder/components/mosaic/mosaic-view');
|
||||
var MosaicItemModel = require('builder/components/mosaic/mosaic-item-model');
|
||||
var BasemapMosaicItemView = require('./basemap-mosaic-item-view');
|
||||
var MosaicAddItemView = require('./basemap-mosaic-add-item-view');
|
||||
|
||||
module.exports = MosaicView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.basemapsCollection) throw new Error('basemapsCollection is required');
|
||||
if (!opts.customBaselayersCollection) throw new Error('customBaselayersCollection is required');
|
||||
if (!opts.modals) throw new Error('modals is required');
|
||||
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._basemapsCollection = opts.basemapsCollection;
|
||||
this._customBaselayersCollection = opts.customBaselayersCollection;
|
||||
this._modals = opts.modals;
|
||||
this._disabled = opts.disabled;
|
||||
this._currentTab = opts.currentTab;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
this._renderList();
|
||||
this._createAddItem();
|
||||
return this;
|
||||
},
|
||||
|
||||
_createItem: function (mdl) {
|
||||
var view = new BasemapMosaicItemView({
|
||||
model: mdl,
|
||||
disabled: this._disabled,
|
||||
basemapsCollection: this._basemapsCollection
|
||||
});
|
||||
this.$('.js-mosaic').append(view.render().el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_createAddItem: function () {
|
||||
var view = new MosaicAddItemView({
|
||||
model: new MosaicItemModel({
|
||||
label: 'Add',
|
||||
val: 'add',
|
||||
template: function () {
|
||||
return '<div class="Mosaic-button">+</div>';
|
||||
}
|
||||
}),
|
||||
layerDefinitionsCollection: this._layerDefinitionsCollection,
|
||||
basemapsCollection: this._basemapsCollection,
|
||||
customBaselayersCollection: this._customBaselayersCollection,
|
||||
mosaicModel: this.model,
|
||||
modals: this._modals,
|
||||
currentTab: this._currentTab
|
||||
});
|
||||
this.$('.js-mosaic').append(view.render().el);
|
||||
this.addView(view);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="js-selector">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.basemap.style.title-label') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m js-highlight"><%- _t('editor.layers.basemap.style.select-style') %></p>
|
||||
</div>
|
||||
@@ -0,0 +1,114 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./basemap-select.tpl');
|
||||
var MosaicFormView = require('builder/components/mosaic-form-view');
|
||||
var BasemapFormView = require('./basemap-form-view');
|
||||
var MosaicCollection = require('builder/components/mosaic/mosaic-collection');
|
||||
var BasemapMosaicFormView = require('./basemap-mosaic-form-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'layerDefinitionsCollection',
|
||||
'basemapsCollection',
|
||||
'customBaselayersCollection',
|
||||
'selectedCategoryVal',
|
||||
'modals'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._baseLayer = this._layerDefinitionsCollection.getBaseLayer();
|
||||
this._disabled = opts.disabled;
|
||||
|
||||
this._initCollection();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
|
||||
var category = this._selectedCategoryVal.toLowerCase();
|
||||
|
||||
switch (category) {
|
||||
case 'color':
|
||||
this._renderForm();
|
||||
break;
|
||||
case 'custom':
|
||||
this._renderCustomMosaic();
|
||||
break;
|
||||
case 'nasa':
|
||||
case 'tilejson':
|
||||
case 'mapbox':
|
||||
case 'wms':
|
||||
this._renderCustomMosaic(category);
|
||||
break;
|
||||
default:
|
||||
this._renderMosaic();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initCollection: function () {
|
||||
this._filteredBasemapsCollection = new MosaicCollection(
|
||||
this._basemapsCollection.findByCategory(this._selectedCategoryVal)
|
||||
);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._filteredBasemapsCollection.bind('change:selected', function (mdl) {
|
||||
if (mdl.get('selected')) {
|
||||
var value = mdl.getValue();
|
||||
|
||||
this._changeBasemap(value);
|
||||
}
|
||||
}, this);
|
||||
this.add_related_model(this._filteredBasemapsCollection);
|
||||
},
|
||||
|
||||
_changeBasemap: function (value) {
|
||||
var basemap = this._basemapsCollection.getByValue(value);
|
||||
|
||||
this._layerDefinitionsCollection.setBaseLayer(basemap.toJSON());
|
||||
},
|
||||
|
||||
_renderMosaic: function () {
|
||||
var view = new MosaicFormView({
|
||||
collection: this._filteredBasemapsCollection,
|
||||
template: require('./basemap-mosaic.tpl'),
|
||||
disabled: this._disabled
|
||||
});
|
||||
this.addView(view);
|
||||
this.$('.js-select').append(view.render().el);
|
||||
},
|
||||
|
||||
_renderCustomMosaic: function (category) {
|
||||
var view = new BasemapMosaicFormView({
|
||||
collection: this._filteredBasemapsCollection,
|
||||
template: require('./basemap-mosaic.tpl'),
|
||||
layerDefinitionsCollection: this._layerDefinitionsCollection,
|
||||
basemapsCollection: this._basemapsCollection,
|
||||
customBaselayersCollection: this._customBaselayersCollection,
|
||||
modals: this._modals,
|
||||
disabled: this._disabled,
|
||||
currentTab: category
|
||||
});
|
||||
this.addView(view);
|
||||
this.$('.js-select').append(view.render().el);
|
||||
},
|
||||
|
||||
_renderForm: function () {
|
||||
var view = new BasemapFormView({
|
||||
model: this._baseLayer,
|
||||
basemapsCollection: this._basemapsCollection,
|
||||
layerDefinitionsCollection: this._layerDefinitionsCollection,
|
||||
disabled: this._disabled
|
||||
});
|
||||
this.addView(view);
|
||||
this.$('.js-select').append(view.render().el);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text js-select"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,78 @@
|
||||
var Backbone = require('backbone');
|
||||
var BasemapModel = require('./basemap-model');
|
||||
var _ = require('underscore');
|
||||
|
||||
/*
|
||||
* Basemap collection, extends Mosaic collection
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
|
||||
model: BasemapModel,
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.bind('change:selected', this._onSelectedChange, this);
|
||||
},
|
||||
|
||||
_onSelectedChange: function (changedModel, isSelected) {
|
||||
if (isSelected) {
|
||||
this.each(function (m) {
|
||||
if (m.cid !== changedModel.cid && m.get('selected')) {
|
||||
m.set('selected', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
findByCategory: function (category) {
|
||||
return this.where({ category: category });
|
||||
},
|
||||
|
||||
getDefaultCategory: function () {
|
||||
var defaultCategory = this.findWhere({ default: true });
|
||||
defaultCategory = defaultCategory || this.first();
|
||||
|
||||
return defaultCategory.get('category');
|
||||
},
|
||||
|
||||
getCategories: function () {
|
||||
var categories = this.chain()
|
||||
.map(function (model) { return model.get('category'); })
|
||||
.concat('Custom')
|
||||
.concat('NASA')
|
||||
.concat('TileJSON')
|
||||
.concat('Mapbox')
|
||||
.concat('WMS')
|
||||
.uniq()
|
||||
.value();
|
||||
|
||||
return categories;
|
||||
},
|
||||
|
||||
getSelected: function () {
|
||||
return this.findWhere({ selected: true });
|
||||
},
|
||||
|
||||
updateSelected: function (value) {
|
||||
var newSelected = this.getByValue(value);
|
||||
newSelected.set({ selected: true });
|
||||
},
|
||||
|
||||
updateCategory: function (value, category) {
|
||||
var newCategory = this.getByValue(value);
|
||||
newCategory.set({ category: category });
|
||||
},
|
||||
|
||||
getByValue: function (value) {
|
||||
return _.first(this.where({ val: value }));
|
||||
},
|
||||
|
||||
getThumbnailImage: function (urlTemplate, subdomains) {
|
||||
return this._lowerXYZ(urlTemplate, subdomains);
|
||||
}
|
||||
|
||||
});
|
||||
2
lib/assets/javascripts/builder/editor/layers/basemap-content.tpl
Executable file
2
lib/assets/javascripts/builder/editor/layers/basemap-content.tpl
Executable file
@@ -0,0 +1,2 @@
|
||||
<div class="js-basemapHeader"></div>
|
||||
<div class="js-basemapContent Editor-content"></div>
|
||||
28
lib/assets/javascripts/builder/editor/layers/change-view-buttons.tpl
Executable file
28
lib/assets/javascripts/builder/editor/layers/change-view-buttons.tpl
Executable file
@@ -0,0 +1,28 @@
|
||||
<div class="Editor-contextSwitchers js-switchers
|
||||
<% if (context === 'table') { %>Editor-contextSwitchers--table<% } %> ">
|
||||
<ul class="u-flex u-alignRight Editor-contextSwitcher Editor-contextSwitcher--geom js-newGeometryView">
|
||||
<% if (queryGeometryModel === 'point' || !queryGeometryModel) { %>
|
||||
<li class="Editor-contextSwitcherItem js-newGeometryItem <% if (isReadOnly || !isVisible) { %>is-disabled<% } %>">
|
||||
<div class="Editor-contextSwitcherButton js-newGeometry" data-feature-type="point" data-tooltip="<%- _t('editor.edit-feature.add-point') %>">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="a"/><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="c"/><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="e"/></defs><g fill="none" fill-rule="evenodd"><path fill="#FFF" d="M4 3h3v1H4v3H3V4H0V3h3V0h1"/><g transform="rotate(180 2.5 6)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" mask="url(#b)" stroke="#FFF" stroke-width="2"/></g><g transform="rotate(180 6 6)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" mask="url(#d)" stroke="#FFF" stroke-width="2"/></g><g transform="rotate(180 6 2.5)"><mask id="f" fill="#fff"><use xlink:href="#e"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" mask="url(#f)" stroke="#FFF" stroke-width="2"/></g></g></svg>
|
||||
</div>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if (queryGeometryModel === 'line' || !queryGeometryModel) { %>
|
||||
<li class="Editor-contextSwitcherItem js-newGeometryItem <% if (isReadOnly || !isVisible) { %>is-disabled<% } %>">
|
||||
<div class="Editor-contextSwitcherButton js-newGeometry" data-feature-type="line" data-tooltip="<%- _t('editor.edit-feature.add-line') %>">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="a"/><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="c"/></defs><g fill="none" fill-rule="evenodd"><path fill="#FFF" d="M11.5 5l-.707-.707-6.354 6.853.704.708"/><g transform="translate(11 2)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" mask="url(#b)" stroke="#FFF" stroke-width="2"/></g><g transform="translate(2 11)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" mask="url(#d)" stroke="#FFF" stroke-width="2"/></g><path fill="#FFF" d="M3 4H0V3h3V0h1v3h3v1H4v3H3"/></g></svg>
|
||||
</div>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if (queryGeometryModel === 'polygon' || !queryGeometryModel) { %>
|
||||
<li class="Editor-contextSwitcherItem js-newGeometryItem <% if (isReadOnly || !isVisible) { %>is-disabled<% } %>">
|
||||
<div class="Editor-contextSwitcherButton js-newGeometry" data-feature-type="polygon" data-tooltip="<%- _t('editor.edit-feature.add-polygon') %>">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="a"/><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="c"/><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" id="e"/></defs><g fill="none" fill-rule="evenodd"><path d="M13 11.5V5h-1v7h1m-.5-7.5l-.707-.707-7.647 7.353-.353.354.707.707.354-.353M4 3h3v1H4v3H3V4H0V3h3V0h1" fill="#FFF"/><path fill="#FFF" d="M4.5 13H12v-1H4v1"/><g transform="rotate(180 7 7)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" stroke="#FFF" stroke-width="2" mask="url(#b)"/></g><g transform="rotate(180 7 2.5)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" stroke="#FFF" stroke-width="2" mask="url(#d)"/></g><g transform="rotate(180 2.5 7)"><mask id="f" fill="#fff"><use xlink:href="#e"/></mask><path d="M0 2h3v1H0V2zm0-2h3v1H0V0zm2 1h1v1H2V1zM0 1h1v1H0V1z" stroke="#FFF" stroke-width="2" mask="url(#f)"/></g></g></svg>
|
||||
</div>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
|
||||
<div class="u-flex u-alignCenter Editor-contextSwitcher Editor-contextSwitcher--view js-mapTableView"></div>
|
||||
</div>
|
||||
49
lib/assets/javascripts/builder/editor/layers/delete-layer-confirmation.tpl
Executable file
49
lib/assets/javascripts/builder/editor/layers/delete-layer-confirmation.tpl
Executable file
@@ -0,0 +1,49 @@
|
||||
<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.layers.delete.title', { layerName: layerName }) %></h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor"><%= _t('editor.layers.delete.desc', { layerVisName: layerVisName }) %></p>
|
||||
<ul class="Modal-listText">
|
||||
<% if (affectedItemsMessages.length > 0) { %>
|
||||
<li class="Modal-listTextItem">
|
||||
<p class="CDB-Text CDB-Size-large">
|
||||
<%= _t('editor.layers.delete.affected-items') %>
|
||||
<% for (var i = 0; i < affectedItemsMessages.length; i++) { %>
|
||||
<span class="CDB-Text is-semibold"><%- affectedItemsMessages[i] %></span>
|
||||
<% if (i < (affectedItemsMessages.length - 2)) { %>
|
||||
,
|
||||
<% } else if (i == (affectedItemsMessages.length - 2)) { %>
|
||||
<%= _t('editor.layers.delete.and') %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
.</p>
|
||||
</li>
|
||||
<% } %>
|
||||
<li class="Modal-listTextItem">
|
||||
<p class="CDB-Text CDB-Size-large"><%= _t('editor.layers.delete.link-to-export') %></p>
|
||||
</li>
|
||||
</ul>
|
||||
<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.layers.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.layers.delete.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
403
lib/assets/javascripts/builder/editor/layers/edit-feature-content-view.js
Executable file
403
lib/assets/javascripts/builder/editor/layers/edit-feature-content-view.js
Executable file
@@ -0,0 +1,403 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./edit-feature-content.tpl');
|
||||
var EditFeatureActionView = require('./edit-feature-content-views/edit-feature-action-view');
|
||||
var EditFeatureControlView = require('./edit-feature-content-views/edit-feature-control-view');
|
||||
var EditFeatureHeaderView = require('./edit-feature-content-views/edit-feature-header-view');
|
||||
var VisTableModel = require('builder/data/visualization-table-model');
|
||||
var EditFeatureInnerView = require('./edit-feature-content-views/edit-feature-inner-view');
|
||||
var PanelWithOptionsView = require('builder/components/view-options/panel-with-options-view');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var Notifier = require('builder/components/notifier/notifier');
|
||||
var EditFeatureGeometryFormModel = require('./edit-feature-content-views/edit-feature-geometry-form-model');
|
||||
var EditFeatureGeometryPointFormModel = require('./edit-feature-content-views/edit-feature-geometry-point-form-model');
|
||||
var EditFeatureAttributesFormModel = require('./edit-feature-content-views/edit-feature-attributes-form-model');
|
||||
|
||||
var StyleHelper = require('builder/helpers/style');
|
||||
var StyleConstants = require('builder/components/form-components/_constants/_style');
|
||||
var StylesFactory = require('builder/editor/style/styles-factory');
|
||||
|
||||
var Router = require('builder/routes/router');
|
||||
|
||||
var NOTIFICATION_ID = 'editFeatureNotification';
|
||||
var NOTIFICATION_ERROR_TEMPLATE = _.template("<span class='u-errorTextColor'><%- title %></span>");
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'stackLayoutModel',
|
||||
'layerDefinitionModel',
|
||||
'configModel',
|
||||
'mapModeModel',
|
||||
'editorModel',
|
||||
'modals'
|
||||
];
|
||||
|
||||
var GEOMETRY_TYPE = 'simple_geom';
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Editor-content',
|
||||
|
||||
events: {
|
||||
'click .js-back': 'cleanAndGoBack'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
_.each(REQUIRED_OPTS, function (item) {
|
||||
if (opts[item] === undefined) throw new Error(item + ' is required');
|
||||
this['_' + item] = opts[item];
|
||||
}, this);
|
||||
|
||||
this._featureModel = this._mapModeModel.getFeatureDefinition();
|
||||
this._tableName = '';
|
||||
this._url = '';
|
||||
|
||||
this._editorModel.set({
|
||||
edition: false
|
||||
});
|
||||
|
||||
this._getTable();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
if (this._querySchemaModel.get('status') !== 'fetched') {
|
||||
// status can be: fetched, unavailable, fetching
|
||||
this._querySchemaModel.bind('change:status', this.render, this);
|
||||
this._querySchemaModel.fetch();
|
||||
}
|
||||
|
||||
this._queryGeometryModel.bind('change:' + GEOMETRY_TYPE, this._checkNewLayerGeometryTypeDefinition, this);
|
||||
|
||||
this.listenTo(this._editorModel, 'cancelPreviousEditions', this.clean);
|
||||
|
||||
this.listenTo(this._featureModel, 'updateFeature', this._updateForms);
|
||||
this.listenTo(this._featureModel, 'destroyFeature', this._onDestroyFeature);
|
||||
this.listenTo(this._featureModel, 'destroyFeatureSuccess', this._onDestroyFeatureSuccess);
|
||||
this.listenTo(this._featureModel, 'destroyFeatureFailed', this._onDestroyFeatureFailed);
|
||||
this.listenTo(this._featureModel, 'addFeature', this._onAddFeature);
|
||||
this.listenTo(this._featureModel, 'saveFeature', this._onSaveFeature);
|
||||
this.listenTo(this._featureModel, 'saveFeatureSuccess', this._onSaveFeatureSuccess);
|
||||
this.listenTo(this._featureModel, 'saveFeatureFailed', this._onSaveFeatureFailed);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
if (this._featureModel.isNew()) {
|
||||
this._addRow();
|
||||
} else {
|
||||
this._renderInfo();
|
||||
}
|
||||
},
|
||||
|
||||
_updateForms: function () {
|
||||
this._updateGeometry();
|
||||
this._attributesFormModel.trigger('updateFeature');
|
||||
},
|
||||
|
||||
_updateGeometry: function () {
|
||||
var geojson = null;
|
||||
|
||||
try {
|
||||
geojson = JSON.parse(this._featureModel.get('the_geom'));
|
||||
} catch (err) {
|
||||
// if the geom is not a valid json value
|
||||
}
|
||||
|
||||
var attrs;
|
||||
|
||||
if (this._featureModel.isPoint()) {
|
||||
attrs = {
|
||||
lng: geojson && geojson.coordinates[0],
|
||||
lat: geojson && geojson.coordinates[1]
|
||||
};
|
||||
} else {
|
||||
attrs = {
|
||||
the_geom: this._featureModel.get('the_geom')
|
||||
};
|
||||
}
|
||||
|
||||
this._geometryFormModel.set(attrs);
|
||||
this._geometryFormModel.trigger('updateFeature', attrs);
|
||||
},
|
||||
|
||||
_addRow: function () {
|
||||
this._renderInfo();
|
||||
},
|
||||
|
||||
_renderInfo: function () {
|
||||
this._renderHeader();
|
||||
this._renderContent();
|
||||
},
|
||||
|
||||
_renderHeader: function () {
|
||||
if (this._headerView) {
|
||||
this.removeView(this._headerView);
|
||||
this._headerView.clean();
|
||||
}
|
||||
|
||||
this._headerView = new EditFeatureHeaderView({
|
||||
url: this._url,
|
||||
tableName: this._tableName,
|
||||
layerDefinitionModel: this._layerDefinitionModel,
|
||||
model: this._featureModel,
|
||||
modals: this._modals,
|
||||
isNew: this._featureModel.isNew(),
|
||||
backAction: this.cleanAndGoBack.bind(this)
|
||||
});
|
||||
this.addView(this._headerView);
|
||||
this.$('.js-editFeatureHeader').html(this._headerView.render().el);
|
||||
},
|
||||
|
||||
_renderContent: function () {
|
||||
if (this._contentView) {
|
||||
this.removeView(this._contentView);
|
||||
this._contentView.clean();
|
||||
}
|
||||
|
||||
var geojson = null;
|
||||
|
||||
try {
|
||||
geojson = JSON.parse(this._featureModel.get('the_geom'));
|
||||
} catch (err) {
|
||||
// if the geom is not a valid json value
|
||||
}
|
||||
|
||||
if (this._featureModel.isPoint()) {
|
||||
this._geometryFormModel = new EditFeatureGeometryPointFormModel({
|
||||
lng: geojson && geojson.coordinates[0],
|
||||
lat: geojson && geojson.coordinates[1]
|
||||
}, {
|
||||
featureModel: this._featureModel
|
||||
});
|
||||
} else {
|
||||
this._geometryFormModel = new EditFeatureGeometryFormModel({
|
||||
the_geom: this._featureModel.get('the_geom')
|
||||
}, {
|
||||
featureModel: this._featureModel
|
||||
});
|
||||
}
|
||||
|
||||
this._attributesFormModel = new EditFeatureAttributesFormModel(this._featureModel.toJSON(), {
|
||||
featureModel: this._featureModel,
|
||||
columnsCollection: this._sourceNode.querySchemaModel.columnsCollection,
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._layerDefinitionModel.getAnalysisDefinitionNodeModel()
|
||||
});
|
||||
|
||||
this._contentView = new PanelWithOptionsView({
|
||||
className: 'Editor-content',
|
||||
editorModel: this._editorModel,
|
||||
createContentView: function () {
|
||||
return new ScrollView({
|
||||
createContentView: function () {
|
||||
return new EditFeatureInnerView({
|
||||
featureModel: this._featureModel,
|
||||
geometryFormModel: this._geometryFormModel,
|
||||
attributesFormModel: this._attributesFormModel
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
}.bind(this),
|
||||
createControlView: function () {
|
||||
return new EditFeatureControlView();
|
||||
},
|
||||
createActionView: function () {
|
||||
return new EditFeatureActionView({
|
||||
model: this.model,
|
||||
featureModel: this._featureModel,
|
||||
geometryFormModel: this._geometryFormModel,
|
||||
attributesFormModel: this._attributesFormModel
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
this.addView(this._contentView);
|
||||
this.$('.js-editFeatureContent').html(this._contentView.render().el);
|
||||
},
|
||||
|
||||
_onDestroyFeature: function () {
|
||||
if (Notifier.getNotification(NOTIFICATION_ID)) {
|
||||
Notifier.removeNotification(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
this.notification = Notifier.addNotification({
|
||||
id: NOTIFICATION_ID,
|
||||
status: 'loading',
|
||||
info: _t('notifications.edit-feature.destroy.loading'),
|
||||
closable: false
|
||||
});
|
||||
},
|
||||
|
||||
_onDestroyFeatureSuccess: function () {
|
||||
this._onUpdateFeature('destroy');
|
||||
|
||||
this.notification.set({
|
||||
status: 'success',
|
||||
info: _t('notifications.edit-feature.destroy.success'),
|
||||
closable: true
|
||||
});
|
||||
|
||||
var layerId = this._layerDefinitionModel.get('id');
|
||||
Router.goToStyleTab(layerId);
|
||||
},
|
||||
|
||||
_onDestroyFeatureFailed: function (mdl, error) {
|
||||
this.notification.set({
|
||||
status: 'error',
|
||||
info: NOTIFICATION_ERROR_TEMPLATE({
|
||||
title: _t('notifications.edit-feature.destroy.error')
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
|
||||
_onAddFeature: function () {
|
||||
if (Notifier.getNotification(NOTIFICATION_ID)) {
|
||||
Notifier.removeNotification(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
this.notification = Notifier.addNotification({
|
||||
id: NOTIFICATION_ID,
|
||||
status: 'loading',
|
||||
info: _t('notifications.edit-feature.adding'),
|
||||
closable: false
|
||||
});
|
||||
},
|
||||
|
||||
_onSaveFeature: function () {
|
||||
if (Notifier.getNotification(NOTIFICATION_ID)) {
|
||||
Notifier.removeNotification(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
this.notification = Notifier.addNotification({
|
||||
id: NOTIFICATION_ID,
|
||||
status: 'loading',
|
||||
info: _t('notifications.edit-feature.save.loading'),
|
||||
closable: false
|
||||
});
|
||||
},
|
||||
|
||||
_onSaveFeatureSuccess: function (operation, model) {
|
||||
this._onUpdateFeature(operation);
|
||||
this._needReloadVis() && this._mapModeModel.trigger('reloadVis');
|
||||
|
||||
this.notification.set({
|
||||
status: 'success',
|
||||
info: _t('notifications.edit-feature.save.success'),
|
||||
closable: true
|
||||
});
|
||||
|
||||
Router.editFeature(model);
|
||||
},
|
||||
|
||||
_checkNewLayerGeometryTypeDefinition: function (queryGeometryModel, currentGeometryType) {
|
||||
var layerGeometryTypeHasBeenDefined = currentGeometryType && !(queryGeometryModel.previous(GEOMETRY_TYPE));
|
||||
|
||||
if (layerGeometryTypeHasBeenDefined) {
|
||||
var defaultStyle = _.extend({ type: StyleConstants.Type.SIMPLE },
|
||||
StylesFactory.getDefaultStyleAttrsByType(StyleConstants.Type.SIMPLE, currentGeometryType)
|
||||
);
|
||||
this._layerDefinitionModel.styleModel.set(defaultStyle);
|
||||
}
|
||||
},
|
||||
|
||||
_onSaveFeatureFailed: function (mdl, error) {
|
||||
this.notification.set({
|
||||
status: 'error',
|
||||
info: NOTIFICATION_ERROR_TEMPLATE({
|
||||
title: _t('notifications.edit-feature.save.error')
|
||||
}),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
|
||||
_onUpdateFeature: function (operation) {
|
||||
var self = this;
|
||||
this._queryGeometryModel.hasValueAsync()
|
||||
.then(function (hasGeom) {
|
||||
if (operation !== 'save' || !hasGeom) {
|
||||
self._queryGeometryModel.resetFetch();
|
||||
}
|
||||
});
|
||||
|
||||
if (operation === 'add' || operation === 'destroy') {
|
||||
this._rowsCollection.resetFetch();
|
||||
}
|
||||
},
|
||||
|
||||
_needReloadVis: function () {
|
||||
var style = this._layerDefinitionModel.styleModel;
|
||||
var color = StyleHelper.getColorAttribute(style);
|
||||
var size = StyleHelper.getSizeAttribute(style);
|
||||
var changed = _.keys(this._featureModel.changedAttributes());
|
||||
return (color && _.contains(changed, color) || size && _.contains(changed, size));
|
||||
},
|
||||
|
||||
_getTable: function () {
|
||||
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
|
||||
});
|
||||
|
||||
this._querySchemaModel = this._sourceNode.querySchemaModel;
|
||||
this._queryGeometryModel = this._sourceNode.queryGeometryModel;
|
||||
this._rowsCollection = this._sourceNode.queryRowsCollection;
|
||||
}
|
||||
|
||||
if (this._visTableModel) {
|
||||
var tableModel = this._visTableModel.getTableModel();
|
||||
this._tableName = tableModel.getUnquotedName();
|
||||
this._url = this._visTableModel && this._visTableModel.datasetURL();
|
||||
}
|
||||
},
|
||||
|
||||
_getSourceNode: function () {
|
||||
var node = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();
|
||||
var source;
|
||||
if (node.get('type') === 'source') {
|
||||
source = node;
|
||||
} else {
|
||||
var primarySource = node.getPrimarySource();
|
||||
if (primarySource && primarySource.get('type') === 'source') {
|
||||
source = primarySource;
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
},
|
||||
|
||||
_destroyContextOverlay: function () {
|
||||
$('.js-editOverlay').fadeOut(200, function () {
|
||||
$('.js-editOverlay').remove();
|
||||
});
|
||||
},
|
||||
|
||||
cleanAndGoBack: function () {
|
||||
this.clean();
|
||||
this._mapModeModel.enterViewingMode();
|
||||
Router.goToPreviousRoute({
|
||||
options: { replace: true }
|
||||
});
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._destroyContextOverlay();
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
<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.modals.edit-feature.delete.title') %></h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor"><%- _t('components.modals.edit-feature.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('components.modals.edit-feature.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('components.modals.edit-feature.delete.confirm') %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,84 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./edit-feature-action.tpl');
|
||||
|
||||
/**
|
||||
* View representing the apply button for a form
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-save': '_onSaveClicked'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.featureModel) throw new Error('featureModel is required');
|
||||
if (!opts.geometryFormModel) throw new Error('geometryFormModel is required');
|
||||
if (!opts.attributesFormModel) throw new Error('attributesFormModel is required');
|
||||
|
||||
this._featureModel = opts.featureModel;
|
||||
this._geometryFormModel = opts.geometryFormModel;
|
||||
this._attributesFormModel = opts.attributesFormModel;
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template({
|
||||
label: this._featureModel.isNew() ? _t('editor.edit-feature.add') : _t('editor.edit-feature.save'),
|
||||
isDisabled: !this._canSave()
|
||||
}));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.on('change', this.render, this);
|
||||
this._featureModel.on('change', function () {
|
||||
this.model.set('hasChanges', true);
|
||||
}, this);
|
||||
this.add_related_model(this._featureModel);
|
||||
this._geometryFormModel.bind('validate', function (isValid) {
|
||||
this.model.set('isValidGeometry', !isValid);
|
||||
}, this);
|
||||
this.add_related_model(this._geometryFormModel);
|
||||
this._attributesFormModel.bind('validate', function (isValid) {
|
||||
this.model.set('isValidAttributes', !isValid);
|
||||
}, this);
|
||||
this.add_related_model(this._attributesFormModel);
|
||||
},
|
||||
|
||||
_canSave: function () {
|
||||
var hasChanges = this.model.get('hasChanges');
|
||||
var isValidGeometry = this.model.get('isValidGeometry');
|
||||
var isValidAttributes = this.model.get('isValidAttributes');
|
||||
|
||||
return isValidGeometry && isValidAttributes && hasChanges;
|
||||
},
|
||||
|
||||
_onSaveClicked: function () {
|
||||
if (this._canSave()) {
|
||||
this._saveFeature();
|
||||
}
|
||||
},
|
||||
|
||||
_saveFeature: function () {
|
||||
var self = this;
|
||||
|
||||
var operation = this._featureModel.isNew() ? 'add' : 'save';
|
||||
var event = operation + 'Feature';
|
||||
this._featureModel.trigger(event);
|
||||
|
||||
this._featureModel.save({
|
||||
success: function () {
|
||||
self._featureModel.trigger('saveFeatureSuccess', operation, self._featureModel);
|
||||
self.model.set('hasChanges', false);
|
||||
},
|
||||
error: function () {
|
||||
self._featureModel.trigger('saveFeatureFailed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
<button class="CDB-Button CDB-Button--primary js-save <% if (isDisabled) { %>is-disabled<% } %>">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">
|
||||
<%- label %>
|
||||
</span>
|
||||
</button>
|
||||
@@ -0,0 +1,135 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var ColumnRowData = require('builder/data/column-row-data');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var FIELDTYPE_TO_FORMTYPE = {
|
||||
string: 'Text',
|
||||
boolean: 'Radio',
|
||||
number: 'Number',
|
||||
date: 'DateTime',
|
||||
geometry: 'Text'
|
||||
};
|
||||
|
||||
var BLACKLISTED_COLUMNS = ['created_at', 'the_geom', 'the_geom_webmercator', 'updated_at'];
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'featureModel',
|
||||
'columnsCollection',
|
||||
'configModel',
|
||||
'nodeDefModel'
|
||||
];
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
this._generateSchema();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.bind('change', this._onChange, this);
|
||||
},
|
||||
|
||||
_onChange: function () {
|
||||
_.each(this.changed, function (val, key) {
|
||||
this._featureModel.set(key, val);
|
||||
}, this);
|
||||
},
|
||||
|
||||
_getInputType: function (columnRowData) {
|
||||
var rows = columnRowData.getRows();
|
||||
return rows && rows.length ? 'Suggest' : 'Text';
|
||||
},
|
||||
|
||||
_getSupportedColumnType: function (columnType) {
|
||||
var supportedColumnType = _.find(_.keys(FIELDTYPE_TO_FORMTYPE), function (fieldType) {
|
||||
return fieldType === columnType;
|
||||
});
|
||||
|
||||
return supportedColumnType || 'string';
|
||||
},
|
||||
|
||||
_generateSchema: function () {
|
||||
var self = this;
|
||||
|
||||
this.schema = {};
|
||||
|
||||
this._columnsCollection.each(function (model) {
|
||||
var columnName = model.get('name');
|
||||
var columnType = this._getSupportedColumnType(model.get('type'));
|
||||
|
||||
if (!_.contains(BLACKLISTED_COLUMNS, columnName)) {
|
||||
// default field type
|
||||
this.schema[columnName] = {
|
||||
type: FIELDTYPE_TO_FORMTYPE[columnType]
|
||||
};
|
||||
|
||||
if (columnType === 'date') {
|
||||
this.schema[columnName].dialogMode = DialogConstants.Mode.FLOAT;
|
||||
}
|
||||
|
||||
if (columnType === 'string') {
|
||||
var columnRowData = new ColumnRowData({
|
||||
column: columnName
|
||||
}, {
|
||||
nodeDefModel: this._nodeDefModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
columnRowData.bind('columnsFetched', function (rows) {
|
||||
var type = self._getInputType(this);
|
||||
if (self.schema[columnName].type !== type) {
|
||||
self.schema[columnName].type = type;
|
||||
if (type === 'Suggest') {
|
||||
self.schema[columnName].dialogMode = DialogConstants.Mode.FLOAT;
|
||||
}
|
||||
self.schema[columnName].editorAttrs = {
|
||||
showSearch: true,
|
||||
defaultValue: true,
|
||||
allowFreeTextInput: true,
|
||||
collectionData: rows
|
||||
};
|
||||
|
||||
self._onChangeSchema();
|
||||
}
|
||||
});
|
||||
columnRowData.fetch();
|
||||
}
|
||||
|
||||
if (columnType === 'number') {
|
||||
this.schema[columnName].isFormatted = true;
|
||||
this.schema[columnName].showSlider = false;
|
||||
}
|
||||
|
||||
if (columnType === 'boolean') {
|
||||
this.schema[columnName].options = [
|
||||
{
|
||||
label: _t('form-components.editors.radio.true'),
|
||||
val: true
|
||||
}, {
|
||||
label: _t('form-components.editors.radio.false'),
|
||||
val: false
|
||||
}, {
|
||||
label: _t('form-components.editors.radio.null'),
|
||||
val: null
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (columnName === 'cartodb_id') {
|
||||
this.schema[columnName].isFormatted = false;
|
||||
this.schema[columnName].editorAttrs = {
|
||||
disabled: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
_onChangeSchema: function () {
|
||||
this.trigger('changeSchema');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./edit-feature-attributes-form.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
module: 'editor:layers:edit-feature-content-views:edit-feature-attributes-form-view',
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
|
||||
this._generateForms();
|
||||
|
||||
this.model.bind('changeSchema', this._generateForms, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_generateForms: function () {
|
||||
var self = this;
|
||||
|
||||
if (this._formView) {
|
||||
this._formView.remove();
|
||||
}
|
||||
|
||||
this._formView = new Backbone.Form({
|
||||
model: this.model
|
||||
});
|
||||
|
||||
this._formView.bind('change', function () {
|
||||
var validate = this.validate();
|
||||
self.model.trigger('validate', !!validate);
|
||||
|
||||
if (!validate) {
|
||||
this.commit();
|
||||
}
|
||||
});
|
||||
|
||||
this.$('.js-form').append(this._formView.render().$el);
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
if (this._formView) {
|
||||
this._formView.remove();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
<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.edit-feature.attributes') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.edit-feature.attributes-columns') %></p>
|
||||
<div class="js-form"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./edit-feature-control.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
<button class="CDB-Button u-upperCase js-back">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-actionTextColor"><%- _t('editor.edit-feature.cancel') %></span>
|
||||
</button>
|
||||
@@ -0,0 +1,96 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var cdb = require('internal-carto.js');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
_COORDINATES_OPTIONS: {
|
||||
min_lat: -90,
|
||||
max_lat: 90,
|
||||
min_lng: -180,
|
||||
max_lng: 180
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.featureModel) throw new Error('featureModel is required');
|
||||
|
||||
this._featureModel = opts.featureModel;
|
||||
|
||||
this._generateSchema();
|
||||
},
|
||||
|
||||
_formatCoordinate: function (coordinate) {
|
||||
if (this._isValidCoordinate(coordinate)) {
|
||||
return parseFloat(parseFloat(coordinate).toFixed(8));
|
||||
}
|
||||
},
|
||||
|
||||
_isValidCoordinate: function (coordinate) {
|
||||
return _.isNumber(coordinate) || _.isString(coordinate);
|
||||
},
|
||||
|
||||
_getLatitude: function (pos) {
|
||||
return pos && pos[0];
|
||||
},
|
||||
|
||||
_getLongitude: function (pos) {
|
||||
return pos && pos[1];
|
||||
},
|
||||
|
||||
_validateGeom: function (value) {
|
||||
var validation;
|
||||
var coordinates;
|
||||
|
||||
var geoJSON = JSON.parse(value);
|
||||
|
||||
if (this._featureModel.isLine()) {
|
||||
coordinates = cdb.helpers.GeoJSONHelper.getPolylineLatLngsFromGeoJSONCoords(geoJSON);
|
||||
} else {
|
||||
coordinates = cdb.helpers.GeoJSONHelper.getPolygonLatLngsFromGeoJSONCoords(geoJSON);
|
||||
}
|
||||
|
||||
var errors = _.chain(coordinates)
|
||||
.reduce(function (memo, coordinate) {
|
||||
var latitude = this._formatCoordinate(this._getLatitude(coordinate));
|
||||
var longitude = this._formatCoordinate(this._getLongitude(coordinate));
|
||||
|
||||
var latError = (latitude > this._COORDINATES_OPTIONS.max_lat || latitude < this._COORDINATES_OPTIONS.min_lat);
|
||||
|
||||
if (latError) {
|
||||
memo.push(_t('editor.edit-feature.out-of-bounds-lat'));
|
||||
}
|
||||
|
||||
var lngError = (longitude > this._COORDINATES_OPTIONS.max_lng || longitude < this._COORDINATES_OPTIONS.min_lng);
|
||||
|
||||
if (lngError) {
|
||||
memo.push(_t('editor.edit-feature.out-of-bounds-lng'));
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, [], this)
|
||||
.uniq()
|
||||
.value();
|
||||
|
||||
if (errors.length) {
|
||||
validation = {
|
||||
message: errors.join(' ')
|
||||
};
|
||||
}
|
||||
|
||||
return validation;
|
||||
},
|
||||
|
||||
_generateSchema: function () {
|
||||
this.schema = {};
|
||||
|
||||
this.schema.the_geom = {
|
||||
type: 'Text',
|
||||
validators: ['required', this._validateGeom.bind(this)],
|
||||
editorAttrs: {
|
||||
disabled: true
|
||||
},
|
||||
hasCopyButton: this._featureModel.has('the_geom')
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./edit-feature-geometry-form.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template());
|
||||
|
||||
this._generateForms();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_generateForms: function () {
|
||||
if (this._formView) {
|
||||
this._formView.remove();
|
||||
}
|
||||
|
||||
this._formView = new Backbone.Form({
|
||||
model: this.model
|
||||
});
|
||||
|
||||
this._formView.bind('change', this._validateForm, this);
|
||||
|
||||
this.model.bind('updateFeature', function (attrs) {
|
||||
_.each(attrs, function (value, key) {
|
||||
this._formView.fields[key].editor.setValue(value);
|
||||
}, this);
|
||||
this._validateForm();
|
||||
}, this);
|
||||
|
||||
this.$('.js-form').append(this._formView.render().$el);
|
||||
},
|
||||
|
||||
_validateForm: function () {
|
||||
var validate = this._formView.validate();
|
||||
this.model.trigger('validate', !!validate);
|
||||
|
||||
if (!validate) {
|
||||
this._formView.commit();
|
||||
}
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
if (this._formView) {
|
||||
this._formView.remove();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</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.edit-feature.geometry') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.edit-feature.geometry-edit') %></p>
|
||||
<div class="js-form"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,130 @@
|
||||
var _ = require('underscore');
|
||||
var EditFeatureGeometryFormModel = require('./edit-feature-geometry-form-model');
|
||||
|
||||
var getCoordinatesFromGeoJSON = function (geoJSON) {
|
||||
if (geoJSON && geoJSON.coordinates && geoJSON.coordinates.length === 2) {
|
||||
return geoJSON.coordinates;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EditFeatureGeometryFormModel.extend({
|
||||
|
||||
initialize: function () {
|
||||
EditFeatureGeometryFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.bind('change:lat', this._onLatLngChange, this);
|
||||
this.bind('change:lng', this._onLatLngChange, this);
|
||||
},
|
||||
|
||||
_onLatLngChange: function () {
|
||||
var newGeoJSON = this._toGeoJSON();
|
||||
var currentGeoJSON = {};
|
||||
|
||||
if (this._featureModel.get('the_geom')) {
|
||||
currentGeoJSON = JSON.parse(this._featureModel.get('the_geom'));
|
||||
}
|
||||
|
||||
// Only set `the_geom` if there's new geoJSON
|
||||
if (!_.isEqual(newGeoJSON, currentGeoJSON)) {
|
||||
this._featureModel.set('the_geom', JSON.stringify(newGeoJSON));
|
||||
}
|
||||
},
|
||||
|
||||
_toGeoJSON: function () {
|
||||
var latitude = this._formatCoordinate(this._getLatitude());
|
||||
var longitude = this._formatCoordinate(this._getLongitude());
|
||||
if (this._isValidCoordinate(latitude) && this._isValidCoordinate(longitude)) {
|
||||
return {
|
||||
'type': 'Point',
|
||||
'coordinates': [ longitude, latitude ]
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_getLatitude: function () {
|
||||
if (this.has('lat')) {
|
||||
return this.get('lat');
|
||||
}
|
||||
|
||||
var geoJSON = JSON.parse(this._featureModel.get('the_geom'));
|
||||
var geoJSONCoordinates = getCoordinatesFromGeoJSON(geoJSON);
|
||||
var latitudeFromGeoJSON = geoJSONCoordinates && geoJSONCoordinates[1];
|
||||
if (latitudeFromGeoJSON) {
|
||||
return latitudeFromGeoJSON;
|
||||
}
|
||||
},
|
||||
|
||||
_getLongitude: function () {
|
||||
if (this.has('lng')) {
|
||||
return this.get('lng');
|
||||
}
|
||||
|
||||
var geoJSON = JSON.parse(this._featureModel.get('the_geom'));
|
||||
var geoJSONCoordinates = getCoordinatesFromGeoJSON(geoJSON);
|
||||
var longitudeFromGeoJSON = geoJSONCoordinates && geoJSONCoordinates[1];
|
||||
if (longitudeFromGeoJSON) {
|
||||
return longitudeFromGeoJSON;
|
||||
}
|
||||
},
|
||||
|
||||
_validateLatitude: function (value, formValues) {
|
||||
var numericValue = +value;
|
||||
|
||||
var error = {
|
||||
message: _t('editor.edit-feature.valid-lat')
|
||||
};
|
||||
|
||||
if (_.isNumber(numericValue) &&
|
||||
(numericValue >= this._COORDINATES_OPTIONS.min_lat && numericValue <= this._COORDINATES_OPTIONS.max_lat)) {
|
||||
return null; // valid latitude
|
||||
}
|
||||
|
||||
if (_.isNumber(numericValue) &&
|
||||
(numericValue > this._COORDINATES_OPTIONS.max_lat || numericValue < this._COORDINATES_OPTIONS.min_lat)) {
|
||||
error.message = _t('editor.edit-feature.out-of-bounds-lat');
|
||||
}
|
||||
|
||||
return error;
|
||||
},
|
||||
|
||||
_validateLongitude: function (value, formValues) {
|
||||
var numericValue = +value;
|
||||
|
||||
var error = {
|
||||
message: _t('editor.edit-feature.valid-lng')
|
||||
};
|
||||
|
||||
if (_.isNumber(numericValue) &&
|
||||
(numericValue >= this._COORDINATES_OPTIONS.min_lng && numericValue <= this._COORDINATES_OPTIONS.max_lng)) {
|
||||
return null; // valid longitude
|
||||
}
|
||||
|
||||
if (_.isNumber(numericValue) &&
|
||||
(numericValue > this._COORDINATES_OPTIONS.max_lng || numericValue < this._COORDINATES_OPTIONS.min_lng)) {
|
||||
error.message = _t('editor.edit-feature.out-of-bounds-lng');
|
||||
}
|
||||
|
||||
return error;
|
||||
},
|
||||
|
||||
_generateSchema: function () {
|
||||
this.schema = {};
|
||||
|
||||
this.schema.lng = {
|
||||
type: 'Number',
|
||||
validators: ['required', this._validateLongitude.bind(this)],
|
||||
showSlider: false
|
||||
};
|
||||
|
||||
this.schema.lat = {
|
||||
type: 'Number',
|
||||
validators: ['required', this._validateLatitude.bind(this)],
|
||||
showSlider: false
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./edit-feature-header.tpl');
|
||||
var ContextMenuFactory = require('builder/components/context-menu-factory-view');
|
||||
var ConfirmationView = require('builder/components/modals/confirmation/modal-confirmation-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var templateConfirmation = require('./delete-feature-confirmation.tpl');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'url',
|
||||
'tableName',
|
||||
'modals',
|
||||
'isNew',
|
||||
'layerDefinitionModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var letter = this._layerDefinitionModel.get('letter');
|
||||
var featureType = this.model.getFeatureType() ? _t('editor.edit-feature.features.' + this.model.getFeatureType()) : _t('editor.edit-feature.features.geometry');
|
||||
var breadcrumbLabel = this._isNew ? _t('editor.edit-feature.add-' + featureType) : _t('editor.edit-feature.edit', { featureType: featureType });
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
url: this._url,
|
||||
layerName: this._layerDefinitionModel.getName(),
|
||||
tableName: this._tableName,
|
||||
bgColor: this._layerDefinitionModel.getColor(),
|
||||
letter: letter,
|
||||
featureType: featureType,
|
||||
breadcrumbLabel: breadcrumbLabel
|
||||
})
|
||||
);
|
||||
|
||||
if (!this._isNew) {
|
||||
this._initViews();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var menuItems = [{
|
||||
label: _t('editor.edit-feature.delete', { featureType: _t('editor.edit-feature.features.' + this.model.getFeatureType()) }),
|
||||
val: 'delete-feature',
|
||||
destructive: true,
|
||||
action: this._confirmDeleteFeature.bind(this)
|
||||
}];
|
||||
|
||||
this._contextMenuFactory = new ContextMenuFactory({
|
||||
menuItems: menuItems
|
||||
});
|
||||
|
||||
this.$('.js-context-menu').append(this._contextMenuFactory.render().el);
|
||||
this.addView(this._contextMenuFactory);
|
||||
},
|
||||
|
||||
_confirmDeleteFeature: function () {
|
||||
this._modals.create(function (modalModel) {
|
||||
return new ConfirmationView({
|
||||
modalModel: modalModel,
|
||||
template: templateConfirmation,
|
||||
runAction: this._destroyFeature.bind(this)
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_destroyFeature: function () {
|
||||
this.model.trigger('destroyFeature');
|
||||
|
||||
this.model.destroy({
|
||||
success: function () {
|
||||
this.model.trigger('destroyFeatureSuccess');
|
||||
}.bind(this),
|
||||
error: function () {
|
||||
this.model.trigger('destroyFeatureFailed');
|
||||
}.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
<%- breadcrumbLabel %>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="Editor-HeaderInfoEditor Editor-HeaderInfoEditor--layer">
|
||||
<div class="Editor-HeaderInfo-inner Editor-HeaderInfo-inner--wide u-ellipsis">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace">
|
||||
<span class="CDB-SelectorLayer-letter CDB-Text CDB-Size-small u-whiteTextColor u-tSpace--m u-rSpace--m u-upperCase" style="background-color: <%- bgColor %>;">
|
||||
<%- letter %>
|
||||
</span>
|
||||
|
||||
<h2 class="Inline-editor">
|
||||
<div class="CDB-Text CDB-Size-huge is-light u-ellipsis">
|
||||
<%- layerName %>
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="Editor-HeaderInfo-source u-flex">
|
||||
<p class="CDB-Text CDB-Size-small u-ellipsis">
|
||||
<a href="<%- url %>" target="_blank" title="<%- tableName %>" class="Editor-headerLayerName"><%- tableName %></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="u-flex u-tSpace-xl js-context-menu"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var EditFeatureGeometryFormView = require('./edit-feature-geometry-form-view');
|
||||
var EditFeatureAttributesFormView = require('./edit-feature-attributes-form-view');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.featureModel) throw new Error('featureModel is required');
|
||||
if (!opts.geometryFormModel) throw new Error('geometryFormModel is required');
|
||||
if (!opts.attributesFormModel) throw new Error('attributesFormModel is required');
|
||||
|
||||
this._featureModel = opts.featureModel;
|
||||
this._geometryFormModel = opts.geometryFormModel;
|
||||
this._attributesFormModel = opts.attributesFormModel;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (this._editFeatureGeometryFormView) {
|
||||
this._editFeatureGeometryFormView.clean();
|
||||
}
|
||||
|
||||
if (this._editFeatureAttributesFormView) {
|
||||
this._editFeatureAttributesFormView.clean();
|
||||
}
|
||||
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.empty();
|
||||
|
||||
this._editFeatureGeometryFormView = new EditFeatureGeometryFormView({
|
||||
model: this._geometryFormModel
|
||||
});
|
||||
|
||||
this.addView(this._editFeatureGeometryFormView);
|
||||
this.$el.append(this._editFeatureGeometryFormView.render().el);
|
||||
|
||||
this._editFeatureAttributesFormView = new EditFeatureAttributesFormView({
|
||||
model: this._attributesFormModel
|
||||
});
|
||||
|
||||
this.addView(this._editFeatureAttributesFormView);
|
||||
this.$el.append(this._editFeatureAttributesFormView.render().el);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
2
lib/assets/javascripts/builder/editor/layers/edit-feature-content.tpl
Executable file
2
lib/assets/javascripts/builder/editor/layers/edit-feature-content.tpl
Executable file
@@ -0,0 +1,2 @@
|
||||
<div class="js-editFeatureHeader"></div>
|
||||
<div class="Editor-content js-editFeatureContent"></div>
|
||||
@@ -0,0 +1 @@
|
||||
<div class="EditOverlay js-editOverlay is-hidden"><p class="EditOverlay-inner CDB-Text CDB-Size-medium u-whiteTextColor"><%- text %></p></div>
|
||||
4
lib/assets/javascripts/builder/editor/layers/inline-editor.tpl
Executable file
4
lib/assets/javascripts/builder/editor/layers/inline-editor.tpl
Executable file
@@ -0,0 +1,4 @@
|
||||
<h2 class="Editor-HeaderInfo-titleText Inline-editor js-title-editor" title="<%- alias %>">
|
||||
<div class="CDB-Text CDB-Size-huge is-light u-ellipsis js-title Inline-editor-text"><%- alias %></div>
|
||||
<input type="text" name="text" class="Inline-editor-input Inline-editor-input--small CDB-Text CDB-InputText js-input" value="<%- alias %>" readonly>
|
||||
</h2>
|
||||
65
lib/assets/javascripts/builder/editor/layers/layer-analyses-view.js
Executable file
65
lib/assets/javascripts/builder/editor/layers/layer-analyses-view.js
Executable file
@@ -0,0 +1,65 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var LayerAnalysisDraggableView = require('./layer-analysis-draggable-view');
|
||||
|
||||
/**
|
||||
* View of analyses within a layer
|
||||
* this.model is the layer-definition-model
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'ul',
|
||||
|
||||
options: {
|
||||
sortableSelector: ''
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!this.options.sortableSelector) throw new Error('sortableSelector is required');
|
||||
if (!opts.layerAnalysisViewFactory) throw new Error('layerAnalysisViewFactory is required');
|
||||
if (!this.model) throw new Error('layerDefinitionModel is required');
|
||||
|
||||
this._layerAnalysisViewFactory = opts.layerAnalysisViewFactory;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
var nodeDefModel = this.model.getAnalysisDefinitionNodeModel();
|
||||
this._renderNode(nodeDefModel);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} current instance of a analysis-layer-node-model
|
||||
*/
|
||||
_renderNode: function (current) {
|
||||
this._createNodeView(current);
|
||||
|
||||
var next = current.getPrimarySource();
|
||||
if (next && this.model.isOwnerOfAnalysisNode(current)) {
|
||||
this._renderNode(next);
|
||||
}
|
||||
},
|
||||
|
||||
_createNodeView: function (current) {
|
||||
var view = this._layerAnalysisViewFactory.createView(current, this.model);
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
this._attachDraggableToNodeView(view, current);
|
||||
},
|
||||
|
||||
_attachDraggableToNodeView: function (nodeView, nodeDefModel) {
|
||||
if (!this.model.isOwnerOfAnalysisNode(nodeDefModel)) return;
|
||||
|
||||
var draggableView = new LayerAnalysisDraggableView({
|
||||
model: nodeDefModel,
|
||||
layerDefinitionModel: this.model,
|
||||
getNextLetter: this.model.collection.nextLetter.bind(this.model.collection),
|
||||
$nodeViewElement: nodeView.$el,
|
||||
sortableSelector: this.options.sortableSelector
|
||||
});
|
||||
this.addView(draggableView);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
require('jquery-ui');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./layer-analysis-draggable.tpl');
|
||||
var analyses = require('builder/data/analyses');
|
||||
var layerColors = require('builder/data/layer-colors');
|
||||
|
||||
/**
|
||||
* Attach draggable behavior to an analysis node
|
||||
* Implemented as a view to hook into Backbone's lifecycle handling, but really it's just attaching the behavior to the
|
||||
* provided $nodeViewElement
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
options: {
|
||||
getNextLetter: function () { return 'x'; },
|
||||
sortableSelector: '',
|
||||
$nodeViewElement: null
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.sortableSelector) throw new Error('sortableSelector is required');
|
||||
if (!opts.$nodeViewElement) throw new Error('$nodeViewElement is required');
|
||||
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
|
||||
|
||||
this.options.$nodeViewElement.draggable({
|
||||
appendTo: this.options.sortableSelector,
|
||||
connectToSortable: this.options.sortableSelector,
|
||||
cursor: 'move',
|
||||
axis: 'y',
|
||||
cursorAt: { top: 40 },
|
||||
helper: this._createHelper.bind(this),
|
||||
zIndex: 1000
|
||||
});
|
||||
},
|
||||
|
||||
_createHelper: function () {
|
||||
var nextLetter = this.options.getNextLetter();
|
||||
var layerLetter = this.options.layerDefinitionModel.get('letter');
|
||||
return template({
|
||||
nodeId: this.model.id,
|
||||
nextLetter: nextLetter,
|
||||
layerLetter: layerLetter,
|
||||
nextBgColor: layerColors.getColorForLetter(nextLetter),
|
||||
title: analyses.title(this.model)
|
||||
});
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
if (this.options.$nodeViewElement && this.options.$nodeViewElement.data('ui-draggable')) {
|
||||
this.options.$nodeViewElement.draggable('destroy');
|
||||
}
|
||||
|
||||
this.options.$nodeViewElement = null; // remove DOM reference to avoid memory leak
|
||||
CoreView.prototype.clean.apply(this, arguments);
|
||||
}
|
||||
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user