Initial commit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const template = require('./delete-organization-user.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'modalModel',
|
||||
'configModel',
|
||||
'passwordNeeded',
|
||||
'organizationUser',
|
||||
'authenticityToken'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-cancel': '_closeDialog',
|
||||
'submit .js-form': '_closeDialog'
|
||||
},
|
||||
|
||||
options: {
|
||||
authenticityToken: '',
|
||||
organizationUser: {}
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
CoreView.prototype.initialize.apply(this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template({
|
||||
username: this._organizationUser.get('username'),
|
||||
formAction: `${this._configModel.prefixUrl()}/organization/users/${this._organizationUser.get('username')}`,
|
||||
authenticityToken: this._authenticityToken,
|
||||
passwordNeeded: this._passwordNeeded
|
||||
}));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_closeDialog: function () {
|
||||
if (this._modalModel) {
|
||||
this._modalModel.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
<form accept-charset="UTF-8" action="<%- formAction %>" method="post" class="js-form">
|
||||
<input name="utf8" type="hidden" value="✓" />
|
||||
<input name="authenticity_token" type="hidden" value="<%- authenticityToken %>" />
|
||||
<input name="_method" type="hidden" value="delete" />
|
||||
|
||||
<div class="CDB-Text Dialog-header u-inner">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--negative">
|
||||
<i class="CDB-IconFont CDB-IconFont-defaultUser"></i>
|
||||
</div>
|
||||
<p class="Dialog-headerTitle">You are about to delete <%- username %>'s account.</p>
|
||||
<p class="Dialog-headerText">
|
||||
By deleting this account all <%- username %>'s maps and datasets will be lost,
|
||||
but extra credits will be reassigned to your user.
|
||||
<% if (passwordNeeded) { %>
|
||||
Type your password, please.
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<% if (passwordNeeded) { %>
|
||||
<div class="CDB-Text Dialog-body">
|
||||
<div class="Form-row Form-row--centered has-label">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="Form-label">Your password</label>
|
||||
</div>
|
||||
<div class="Form-rowData">
|
||||
<input type="password" id="deletion_password_confirmation" name="password_confirmation" class="CDB-InputText CDB-Text Form-input Form-input--long" value=""/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="CDB-Text Dialog-footer u-inner">
|
||||
<button class="CDB-Button CDB-Button--secondary Dialog-footerBtn js-cancel" type="button">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Cancel</span>
|
||||
</button>
|
||||
<button type="submit" class="CDB-Button CDB-Button--error js-ok">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Yes, delete <%- username %> account</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,40 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const template = require('./delete-organization.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'modalModel',
|
||||
'authenticityToken'
|
||||
];
|
||||
/**
|
||||
* When an organization owner wants to delete the full organization
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
options: {
|
||||
authenticityToken: ''
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .js-cancel': '_closeDialog',
|
||||
'submit .js-form': '_closeDialog'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return this.$el.html(template({
|
||||
formAction: `${this._userModel.get('base_url')}/organization`,
|
||||
authenticityToken: this._authenticityToken,
|
||||
passwordNeeded: !!this._userModel.get('needs_password_confirmation')
|
||||
}));
|
||||
},
|
||||
|
||||
_closeDialog: function () {
|
||||
this._modalModel.destroy();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
<form accept-charset="UTF-8" action="<%- formAction %>" method="post" class="js-form">
|
||||
<input name="utf8" type="hidden" value="✓" />
|
||||
<input name="authenticity_token" type="hidden" value="<%- authenticityToken %>" />
|
||||
<input name="_method" type="hidden" value="delete" />
|
||||
|
||||
<div class="CDB-Text Dialog-header u-inner">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--negative">
|
||||
<i class="CDB-IconFont CDB-IconFont-defaultUser"></i>
|
||||
</div>
|
||||
<p class="Dialog-headerTitle">You are about to delete your organization.</p>
|
||||
<p class="Dialog-headerText">
|
||||
You will remove this organization and all its users (including this account)<br/>
|
||||
and it will not be possible to recover its information (including tables and data) after this deletion.<br/>
|
||||
<% if (passwordNeeded) { %>
|
||||
If you want to proceed, type your password:<br/>
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<% if (passwordNeeded) { %>
|
||||
<div class="CDB-Text Dialog-body">
|
||||
<div class="Form-row Form-row--centered has-label">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="Form-label">Your password</label>
|
||||
</div>
|
||||
<div class="Form-rowData">
|
||||
<input type="password" name="deletion_password_confirmation" class="CDB-InputText CDB-Text Form-input Form-input--long" value=""/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="Dialog-footer u-inner">
|
||||
<button type="button" class="CDB-Button CDB-Button--secondary js-cancel">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Cancel</span>
|
||||
</button>
|
||||
<button type="submit" class="CDB-Button CDB-Button--error js-ok">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Yes, delete the organization</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
const _ = require('underscore');
|
||||
const $ = require('jquery');
|
||||
const Backbone = require('backbone');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const PagedSearchView = require('dashboard/components/paged-search/paged-search-view');
|
||||
const PagedSearchModel = require('dashboard/data/paged-search-model');
|
||||
const PasswordValidatedForm = require('dashboard/helpers/password-validated-form');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const template = require('./add-group-users-view.tpl');
|
||||
const loadingView = require('builder/components/loading/render-loading.js');
|
||||
const requestErrorTemplate = require('dashboard/views/data-library/content/request-error-template.tpl');
|
||||
const responseParser = require('dashboard/helpers/response-parser');
|
||||
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
|
||||
const GroupUsersListView = require('dashboard/views/organization/groups-admin/group-users-list/group-users-list-view');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'group',
|
||||
'orgUsers',
|
||||
'userModel',
|
||||
'modalModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Dialog to add custom basemap to current map.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .ok': 'ok'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
this.model = new Backbone.Model();
|
||||
|
||||
// Include current user in fetch results
|
||||
this._orgUsers.excludeCurrentUser(false);
|
||||
|
||||
this._modals = new ModalsServiceModel();
|
||||
|
||||
this._initBinds();
|
||||
this._initViews();
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
// restore org users
|
||||
this._orgUsers.restoreExcludeCurrentUser();
|
||||
CoreView.prototype.clean.apply(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override cdb.ui.common.Dialog.prototype.render
|
||||
*/
|
||||
render: function () {
|
||||
this.$el.html(this.render_content());
|
||||
this.$el.addClass('Dialog-contentWrapper');
|
||||
this._onChangeSelected();
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @implements cdb.ui.common.Dialog.prototype.render_content
|
||||
*/
|
||||
render_content: function () {
|
||||
switch (this.model.get('state')) {
|
||||
case 'saving':
|
||||
return loadingView({
|
||||
title: 'Adding users to group'
|
||||
});
|
||||
case 'passwordConfirmationFail':
|
||||
return requestErrorTemplate({
|
||||
msg: this.model.get('failMessage')
|
||||
});
|
||||
case 'saveFail':
|
||||
return errorTemplate({
|
||||
msg: ''
|
||||
});
|
||||
default:
|
||||
const $content = $(template());
|
||||
$content.find('.js-dlg-body').replaceWith(this._PagedSearchView.render().el);
|
||||
return $content;
|
||||
}
|
||||
},
|
||||
|
||||
ok: function () {
|
||||
const selectedUsers = this._selectedUsers();
|
||||
|
||||
if (!selectedUsers.length) return;
|
||||
|
||||
if (!this._userModel.needsPasswordConfirmation()) {
|
||||
this.model.set('state', 'saving');
|
||||
return this._addUsers();
|
||||
}
|
||||
|
||||
PasswordValidatedForm.showPasswordModal({
|
||||
modalService: this._modals,
|
||||
onPasswordTyped: password => {
|
||||
this.model.set('state', 'saving');
|
||||
this._addUsers(password);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._PagedSearchView = new PagedSearchView({
|
||||
isUsedInDialog: true,
|
||||
pagedSearchModel: new PagedSearchModel({
|
||||
per_page: 50,
|
||||
order: 'username'
|
||||
}),
|
||||
collection: this._orgUsers,
|
||||
createListView: this._createUsersListView.bind(this)
|
||||
});
|
||||
|
||||
this.addView(this._PagedSearchView);
|
||||
},
|
||||
|
||||
_createUsersListView: function () {
|
||||
return new GroupUsersListView({
|
||||
users: this._orgUsers
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._orgUsers, 'change:selected', this._onChangeSelected);
|
||||
this.listenTo(this.model, 'change:state', this.render);
|
||||
},
|
||||
|
||||
_onChangeSelected: function () {
|
||||
this.$('.ok').toggleClass('is-disabled', this._selectedUsers().length === 0);
|
||||
},
|
||||
|
||||
_selectedUsers: function () {
|
||||
return this._orgUsers.where({ selected: true });
|
||||
},
|
||||
|
||||
_addUsers: function (password) {
|
||||
const selectedUsers = this._selectedUsers();
|
||||
const ids = _.pluck(selectedUsers, 'id');
|
||||
|
||||
this._group.users.addInBatch(ids, password)
|
||||
.done(() => {
|
||||
this._group.users.add(selectedUsers);
|
||||
this._modalModel.destroy();
|
||||
})
|
||||
.fail(response => {
|
||||
const errors = responseParser(response) || '';
|
||||
|
||||
if (errors.indexOf('Confirmation password') > -1) {
|
||||
return this.model.set({
|
||||
state: 'passwordConfirmationFail',
|
||||
failMessage: errors || ''
|
||||
});
|
||||
}
|
||||
|
||||
this.model.set({ state: 'saveFail' });
|
||||
});
|
||||
}
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<div class="CDB-Text Dialog-content Dialog-content--expanded">
|
||||
<div class="Dialog-header Dialog-header--expanded CreateDialog-header">
|
||||
<ul class="CreateDialog-headerSteps">
|
||||
<li class="CreateDialog-headerStep CreateDialog-headerStep--single">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--neutral">
|
||||
<i class="CDB-IconFont CDB-IconFont-boss"></i>
|
||||
</div>
|
||||
<p class="Dialog-headerTitle">Add users to this group</p>
|
||||
<p class="Dialog-headerText">When sharing a dataset or map with a group all the users within that group will get the same permissions.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="CDB-Text js-dlg-body"></div>
|
||||
|
||||
<div class="Dialog-stickyFooter">
|
||||
<div class="Dialog-footer ChangePrivacy-shareFooter u-inner">
|
||||
<div></div>
|
||||
<button class="CDB-Button CDB-Button--primary ok is-disabled">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Add users</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
const Backbone = require('backbone');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const loadingView = require('builder/components/loading/render-loading');
|
||||
const createGroupTemplate = require('./create-group.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'group',
|
||||
'onCreated',
|
||||
'flashMessageModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* View to create a new group for an organization.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'form',
|
||||
|
||||
events: {
|
||||
'click .js-create': '_onClickCreate',
|
||||
'keyup .js-name': '_onChangeName'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this.model = new Backbone.Model();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (this.model.get('isLoading')) {
|
||||
this.$el.html(
|
||||
loadingView({
|
||||
title: 'Creating group'
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.$el.html(createGroupTemplate());
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:isLoading', this.render);
|
||||
},
|
||||
|
||||
_onClickCreate: function (ev) {
|
||||
this.killEvent(ev);
|
||||
|
||||
const name = this._name();
|
||||
|
||||
if (name) {
|
||||
this.model.set('isLoading', true);
|
||||
|
||||
this._group.save(
|
||||
{ display_name: name },
|
||||
{
|
||||
wait: true,
|
||||
success: this._onCreated,
|
||||
error: this._showErrors.bind(this)
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_showErrors: function (message, response, request) {
|
||||
this.model.set('isLoading', false);
|
||||
|
||||
let flashMessage = 'Could not create group for some unknown reason, please try again';
|
||||
let jsonData;
|
||||
|
||||
try {
|
||||
jsonData = response && JSON.parse(response.responseText);
|
||||
} catch (e) {
|
||||
jsonData = {};
|
||||
}
|
||||
|
||||
if (jsonData && jsonData.errors) {
|
||||
flashMessage = jsonData.errors.join('. ');
|
||||
}
|
||||
|
||||
this._flashMessageModel.show(flashMessage, 'error');
|
||||
},
|
||||
|
||||
_onChangeName: function () {
|
||||
this._flashMessageModel.hide();
|
||||
this.$('.js-create').toggleClass('is-disabled', this._name().length === 0);
|
||||
},
|
||||
|
||||
_name: function () {
|
||||
return this.$('.js-name').val();
|
||||
}
|
||||
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<div class="FormAccount-separator"></div>
|
||||
<div class="FormAccount-row">
|
||||
<div class="FormAccount-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor" for="group-name">Group name</label>
|
||||
</div>
|
||||
<div class="FormAccount-rowData">
|
||||
<input type="text" class="CDB-InputText CDB-Text js-name" id="group-name" size="30" maxlength="28" placeholder="Enter a name of group" />
|
||||
<div class="FormAccount-rowInfo FormAccount-rowInfo--marginLeft">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="FormAccount-footer u-justifyEnd">
|
||||
<button class="CDB-Button CDB-Button--primary is-disabled js-create">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Create group</span>
|
||||
</button>
|
||||
</div>
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
const Backbone = require('backbone');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const loadingView = require('builder/components/loading/render-loading');
|
||||
const PasswordValidatedForm = require('dashboard/helpers/password-validated-form');
|
||||
const template = require('./edit-group.tpl');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'group',
|
||||
'userModel',
|
||||
'flashMessageModel',
|
||||
'modals',
|
||||
'onSaved',
|
||||
'onDeleted'
|
||||
];
|
||||
|
||||
/**
|
||||
* View to edit an organization group.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
tagName: 'form',
|
||||
|
||||
events: {
|
||||
'click .js-delete': '_onClickDelete',
|
||||
'click .js-save': '_onClickSave',
|
||||
'submit form': '_onClickSave',
|
||||
'keyup .js-name': '_onChangeName'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this.model = new Backbone.Model();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (this.model.get('isLoading')) {
|
||||
this.$el.html(
|
||||
loadingView({
|
||||
title: this.model.get('loadingText')
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.$el.html(
|
||||
template({ displayName: this._group.get('display_name') })
|
||||
);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:isLoading', this.render);
|
||||
},
|
||||
|
||||
_onClickSave: function (ev) {
|
||||
this.killEvent(ev);
|
||||
|
||||
const name = this._name();
|
||||
|
||||
if (name && name !== this._group.get('display_name')) {
|
||||
this._setLoading('Saving changes');
|
||||
this._group.save(
|
||||
{ display_name: name },
|
||||
{
|
||||
wait: true,
|
||||
success: this._onSaved,
|
||||
error: this._showErrors.bind(this)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onClickDelete: function (ev) {
|
||||
this.killEvent(ev);
|
||||
|
||||
if (!this._userModel.needsPasswordConfirmation()) {
|
||||
return this._destroyGroup();
|
||||
}
|
||||
|
||||
PasswordValidatedForm.showPasswordModal({
|
||||
modalService: this._modals,
|
||||
onPasswordTyped: password => this._destroyGroup(password)
|
||||
});
|
||||
},
|
||||
|
||||
_destroyGroup: function (password) {
|
||||
this._setLoading('Deleting group');
|
||||
|
||||
this._group.destroy({
|
||||
wait: true,
|
||||
data: JSON.stringify({
|
||||
...this._group.attributes,
|
||||
password_confirmation: password
|
||||
}),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
success: this._onDeleted,
|
||||
error: this._showErrors.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
_setLoading: function (msg) {
|
||||
this._flashMessageModel.hide();
|
||||
|
||||
this.model.set({
|
||||
isLoading: !!msg,
|
||||
loadingText: msg
|
||||
});
|
||||
},
|
||||
|
||||
_showErrors: function (message, response, request) {
|
||||
this._setLoading('');
|
||||
|
||||
let flashMessage = 'Could not update group for some unknown reason, please try again';
|
||||
let jsonData;
|
||||
|
||||
try {
|
||||
jsonData = response && JSON.parse(response.responseText);
|
||||
} catch (e) {
|
||||
jsonData = {};
|
||||
}
|
||||
|
||||
if (jsonData && jsonData.errors) {
|
||||
flashMessage = jsonData.errors.join('. ');
|
||||
}
|
||||
|
||||
this._flashMessageModel.show(flashMessage, 'error');
|
||||
},
|
||||
|
||||
_onChangeName: function () {
|
||||
this.$('.js-save').toggleClass('is-disabled', this._name().length === 0);
|
||||
this._flashMessageModel.hide();
|
||||
},
|
||||
|
||||
_name: function () {
|
||||
return this.$('.js-name').val();
|
||||
}
|
||||
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<div class="FormAccount-separator"></div>
|
||||
|
||||
<div class="FormAccount-row">
|
||||
<div class="FormAccount-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor" for="group-name">Group name</label>
|
||||
</div>
|
||||
<div class="FormAccount-rowData">
|
||||
<input type="text" class="CDB-InputText CDB-Text FormAccount-input FormAccount-input--med js-name" id="group-name" size="30" maxlength="28" value="<%- displayName %>" placeholder="Enter a name of group" />
|
||||
<div class="FormAccount-rowInfo FormAccount-rowInfo--marginLeft">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="FormAccount-footer">
|
||||
<button type="submit" class="CDB-Button CDB-Button--error js-delete">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Delete this group</span>
|
||||
</button>
|
||||
|
||||
<button type="submit" class="CDB-Button CDB-Button--primary js-save">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Save changes</span>
|
||||
</button>
|
||||
</div>
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
const _ = require('underscore');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const AddGroupUsersView = require('dashboard/views/organization/groups-admin/add-group-users/add-group-users-view');
|
||||
const template = require('./add-or-remove-group-users-filters-extra.tpl');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const PasswordValidatedForm = require('dashboard/helpers/password-validated-form');
|
||||
const loadingView = require('builder/components/loading/render-loading.js');
|
||||
const requestErrorTemplate = require('dashboard/views/data-library/content/request-error-template.tpl');
|
||||
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
|
||||
const responseParser = require('dashboard/helpers/response-parser');
|
||||
const randomQuote = require('builder/components/loading/random-quote');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'group',
|
||||
'orgUsers',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* View for the add/remove button in the filters part.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Filters-group',
|
||||
|
||||
events: {
|
||||
'click .js-add-users': '_onClickAddUsers',
|
||||
'click .js-rm-users': '_onClickRemoveUsers'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._modals = new ModalsServiceModel();
|
||||
|
||||
this.listenTo(this._group.users, 'change:selected', this._onChangeSelectedUser);
|
||||
this.listenTo(this._group.users, 'add remove reset', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template());
|
||||
return this;
|
||||
},
|
||||
|
||||
_onClickAddUsers: function (event) {
|
||||
this.killEvent(event);
|
||||
this._openAddGroupsUsersDialog();
|
||||
},
|
||||
|
||||
_openAddGroupsUsersDialog: function () {
|
||||
this._modals.create(modalModel => {
|
||||
return new AddGroupUsersView({
|
||||
group: this._group,
|
||||
orgUsers: this._orgUsers,
|
||||
modalModel,
|
||||
userModel: this._userModel
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onChangeSelectedUser: function () {
|
||||
const hasSelectedUsers = this._selectedUsers().length > 0;
|
||||
this.$('.js-add-users').toggle(!hasSelectedUsers);
|
||||
this.$('.js-rm-users').toggle(hasSelectedUsers);
|
||||
},
|
||||
|
||||
_onClickRemoveUsers: function (ev) {
|
||||
this.killEvent(ev);
|
||||
|
||||
if (!this._userModel.needsPasswordConfirmation()) {
|
||||
return this._removeUsers();
|
||||
}
|
||||
|
||||
PasswordValidatedForm.showPasswordModal({
|
||||
modalService: this._modals,
|
||||
onPasswordTyped: password => this._removeUsers(password)
|
||||
});
|
||||
},
|
||||
|
||||
_removeUsers: function (password) {
|
||||
const selectedUsers = this._selectedUsers();
|
||||
|
||||
if (selectedUsers.length > 0) {
|
||||
const userIds = _.pluck(selectedUsers, 'id');
|
||||
|
||||
const modalModel = this._modals.create(() => {
|
||||
return this._createLoadingView();
|
||||
});
|
||||
|
||||
this._group.users.removeInBatch(userIds, password)
|
||||
.always(function () {
|
||||
modalModel.destroy();
|
||||
})
|
||||
.fail(response => {
|
||||
modalModel.destroy();
|
||||
|
||||
const errors = responseParser(response) || '';
|
||||
|
||||
if (errors.indexOf('Confirmation password') > -1) {
|
||||
return this._modals.create(function (modalModel) {
|
||||
return ViewFactory.createByHTML(requestErrorTemplate({ msg: errors }));
|
||||
});
|
||||
}
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return ViewFactory.createByHTML(errorTemplate({ msg: errors }));
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_createLoadingView: function () {
|
||||
return ViewFactory.createByHTML(loadingView({
|
||||
title: 'Removing users',
|
||||
descHTML: randomQuote()
|
||||
}));
|
||||
},
|
||||
|
||||
_selectedUsers: function () {
|
||||
return this._group.users.where({ selected: true });
|
||||
}
|
||||
});
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<button class="CDB-Button CDB-Button--primary js-add-users">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Add users</span>
|
||||
</button>
|
||||
<button class="CDB-Button CDB-Button--error js-rm-users" style="display: none;">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Remove from group</span>
|
||||
</button>
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
const _ = require('underscore');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
const template = require('./empty-group-filters-extra.tpl');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const PasswordValidatedForm = require('dashboard/helpers/password-validated-form');
|
||||
const loadingView = require('builder/components/loading/render-loading.js');
|
||||
const requestErrorTemplate = require('dashboard/views/data-library/content/request-error-template.tpl');
|
||||
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
|
||||
const responseParser = require('dashboard/helpers/response-parser');
|
||||
const randomQuote = require('builder/components/loading/random-quote');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'groupUsers',
|
||||
'orgUsers',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* View for the add users button and state.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
className: 'Filters-group',
|
||||
|
||||
events: {
|
||||
'click .js-add-users': '_onClickAddUsers'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._modals = new ModalsServiceModel();
|
||||
|
||||
// Init binds
|
||||
this.listenTo(this._orgUsers, 'change:selected', this._onChangeSelectedUser);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template());
|
||||
return this;
|
||||
},
|
||||
|
||||
_onChangeSelectedUser: function () {
|
||||
this.$('.js-add-users').toggleClass('is-disabled', this._selectedUsers().length === 0);
|
||||
},
|
||||
|
||||
_onClickAddUsers: function (ev) {
|
||||
this.killEvent(ev);
|
||||
|
||||
if (!this._userModel.needsPasswordConfirmation()) {
|
||||
return this._addUsers();
|
||||
}
|
||||
|
||||
PasswordValidatedForm.showPasswordModal({
|
||||
modalService: this._modals,
|
||||
onPasswordTyped: password => this._addUsers(password)
|
||||
});
|
||||
},
|
||||
|
||||
_addUsers: function (password) {
|
||||
const selectedUsers = this._selectedUsers();
|
||||
|
||||
if (selectedUsers.length > 0) {
|
||||
const userIds = _.pluck(selectedUsers, 'id');
|
||||
|
||||
const modalModel = this._modals.create(modalModel => {
|
||||
return this._createLoadingView();
|
||||
});
|
||||
|
||||
this._groupUsers.addInBatch(userIds, password)
|
||||
.always(() => modalModel.destroy())
|
||||
.fail(response => {
|
||||
const errors = responseParser(response) || '';
|
||||
|
||||
if (errors.indexOf('Confirmation password') > -1) {
|
||||
return this._modals.create(function (modalModel) {
|
||||
return ViewFactory.createByHTML(requestErrorTemplate({ msg: errors }));
|
||||
});
|
||||
}
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return ViewFactory.createByHTML(errorTemplate({ msg: errors }));
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_createLoadingView: function () {
|
||||
return ViewFactory.createByHTML(loadingView({
|
||||
title: 'Adding users',
|
||||
descHTML: randomQuote()
|
||||
}));
|
||||
},
|
||||
|
||||
_selectedUsers: function () {
|
||||
return this._orgUsers.where({ selected: true });
|
||||
}
|
||||
|
||||
});
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<button class="CDB-Button CDB-Button--primary is-disabled js-add-users">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Add users</span>
|
||||
</button>
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const pluralizeString = require('dashboard/helpers/pluralize');
|
||||
const template = require('./group-header.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'group',
|
||||
'urls'
|
||||
];
|
||||
|
||||
/**
|
||||
* Header view when looking at details of a specific group.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._group, 'change:display_name', this.render);
|
||||
this.listenTo(this._group.users, 'reset add remove', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this._$orgSubheader().hide();
|
||||
|
||||
const isNewGroup = this._group.isNew();
|
||||
const templateData = {
|
||||
backUrl: this._urls.root,
|
||||
title: this._group.get('display_name') || 'Create new group',
|
||||
isNewGroup: isNewGroup,
|
||||
usersUrl: false
|
||||
};
|
||||
|
||||
if (isNewGroup) {
|
||||
templateData.editUrl = window.location;
|
||||
templateData.editUrl.isCurrent = true;
|
||||
} else {
|
||||
templateData.editUrl = this._urls.edit;
|
||||
templateData.usersUrl = this._urls.users;
|
||||
|
||||
const usersCount = this._group.users.length;
|
||||
templateData.usersLabel = usersCount === 0 ? 'Users' : `${usersCount} ${pluralizeString('User', 'Users', usersCount)}`;
|
||||
|
||||
if (!this._urls.users.isCurrent) {
|
||||
templateData.backUrl = this._urls.users;
|
||||
}
|
||||
}
|
||||
|
||||
this.$el.html(template(templateData));
|
||||
return this;
|
||||
},
|
||||
|
||||
_$orgSubheader: function () {
|
||||
return $('.js-org-subheader');
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._$orgSubheader().show();
|
||||
CoreView.prototype.clean.call(this);
|
||||
}
|
||||
});
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<div class="Filters is-relative">
|
||||
<span class="Filters-separator"></span>
|
||||
<div class="Filters-inner">
|
||||
<div class="Filters-row">
|
||||
<ul class="Filters-group CDB-Text CDB-Size-medium">
|
||||
<li class="u-flex u-alignCenter">
|
||||
<a href="<%- backUrl %>" class="u-actionTextColor u-flex u-alignCenter">
|
||||
<i class="CDB-IconFont CDB-IconFont-arrowPrev u-rSpace--xl"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="Filters-typeItem">
|
||||
<div class="FormAccount-title">
|
||||
<p class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor"><%- title %></p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="Filters-group CDB-Text CDB-Size-medium">
|
||||
<% if (usersUrl) { %>
|
||||
<li class="Filters-typeItem">
|
||||
<a href="<%- usersUrl %>" class="CDB-Text CDB-Size-medium u-mainTextColor Filters-typeLink <%- usersUrl.isCurrent ? 'is-selected' : '' %>">
|
||||
<%- usersLabel %>
|
||||
</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<li class="Filters-typeItem">
|
||||
<a href="<%- editUrl %>" class="CDB-Text CDB-Size-medium u-mainTextColor Filters-typeLink <%- editUrl.isCurrent ? 'is-selected' : '' %>">
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<a href="<%- createGroupUrl %>" class="CDB-Button CDB-Button--primary">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Create new group</span>
|
||||
</a>
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const GroupsListView = require('dashboard/views/organization/groups-admin/groups-list/groups-list-view');
|
||||
const groupsIndexFiltersExtraTemplate = require('./group-index-filters-extra.tpl');
|
||||
const PagedSearchView = require('dashboard/components/paged-search/paged-search-view');
|
||||
const PagedSearchModel = require('dashboard/data/paged-search-model');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'groups',
|
||||
'router',
|
||||
'newGroupUrl'
|
||||
];
|
||||
|
||||
/**
|
||||
* Index view of groups to list groups of an organization
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
const pagedSearchView = new PagedSearchView({
|
||||
pagedSearchModel: new PagedSearchModel({
|
||||
fetch_users: true,
|
||||
fetch_shared_maps_count: true,
|
||||
fetch_shared_tables_count: true
|
||||
}),
|
||||
collection: this._groups,
|
||||
createListView: this._createGroupsView.bind(this),
|
||||
thinFilters: true,
|
||||
filtersExtrasView: this._createFiltersExtraView(),
|
||||
noResults: {
|
||||
icon: 'CDB-IconFont-group',
|
||||
title: 'You have not created any groups yet',
|
||||
msg: 'Creating groups enables you to visualize and search for user members assigned to a business group or team in your organization.'
|
||||
}
|
||||
});
|
||||
this.addView(pagedSearchView);
|
||||
|
||||
this.$el.empty();
|
||||
this.$el.append(pagedSearchView.render().el);
|
||||
return this;
|
||||
},
|
||||
|
||||
_createGroupsView: function () {
|
||||
return new GroupsListView({
|
||||
groups: this._groups,
|
||||
newGroupUrl: this._newGroupUrl
|
||||
});
|
||||
},
|
||||
|
||||
_createFiltersExtraView: function () {
|
||||
return ViewFactory.createByTemplate(groupsIndexFiltersExtraTemplate, {
|
||||
createGroupUrl: this._router._rootUrl.urlToPath('new')
|
||||
}, {
|
||||
className: 'Filters-group'
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const template = require('./group-user.tpl');
|
||||
const pluralizeStr = require('dashboard/helpers/pluralize');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'model'
|
||||
];
|
||||
|
||||
/**
|
||||
* View of a single group user.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
tagName: 'li',
|
||||
className: 'OrganizationList-user is-selectable',
|
||||
events: {
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:selected', this._onChangeSelected);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
avatarUrl: this.model.get('avatar_url'),
|
||||
username: this.model.get('username'),
|
||||
email: this.model.get('email'),
|
||||
maps_count: pluralizeStr.prefixWithCount('map', 'maps', this.model.get('all_visualization_count')),
|
||||
table_count: pluralizeStr.prefixWithCount('dataset', 'datasets', this.model.get('table_count'))
|
||||
})
|
||||
);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onChangeSelected: function (model, isSelected) {
|
||||
this.$el.toggleClass('is-selected', !!isSelected);
|
||||
},
|
||||
|
||||
_onClick: function (ev) {
|
||||
this.killEvent(ev);
|
||||
this.model.set('selected', !this.model.get('selected'));
|
||||
}
|
||||
|
||||
});
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<a class="CDB-Text OrganizationList-userLink">
|
||||
<div class="OrganizationList-userAvatar UserAvatar">
|
||||
<img src="<%- avatarUrl %>" alt="<%- username %>" src="<%- username %>" class="UserAvatar-img UserAvatar-img--medium-large OrganizationList-userAvatar--img" />
|
||||
</div>
|
||||
<div class="OrganizationList-userInfo">
|
||||
<div class="OrganizationList-userInfoName">
|
||||
<h3 class="CDB-Size-medium OrganizationList-userInfoTitle u-ellipsLongText" title="<%- username %>">
|
||||
<%- username %>
|
||||
</h3>
|
||||
<h4 class="CDB-Text CDB-Size-small u-ellipis u-secondaryTextColor" title="<%- email %>">
|
||||
<%- email %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="OrganizationList-userInfoData CDB-Text CDB-Size-small u-altTextColor u-alignCenter">
|
||||
<p class="u-flex u-alignCenter">
|
||||
<i class="CDB-IconFont CDB-IconFont-map OrganizationList-userInfoData--paragraph-icon"></i>
|
||||
<%- maps_count %>
|
||||
</p>
|
||||
<p class="u-flex u-alignCenter u-lSpace--xl">
|
||||
<i class="CDB-IconFont CDB-IconFont-rows OrganizationList-userInfoData--paragraph-icon"></i>
|
||||
<%- table_count %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const GroupUserView = require('dashboard/views/organization/groups-admin/group-user/group-user-view');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'users'
|
||||
];
|
||||
|
||||
/**
|
||||
* View of group users.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
tagName: 'ul',
|
||||
className: 'OrganizationList',
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
// init binds
|
||||
this.listenTo(this._users, 'reset add remove', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this._renderUsers();
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderUsers: function () {
|
||||
this._users.each(this._createUserView, this);
|
||||
},
|
||||
|
||||
_createUserView: function (user) {
|
||||
const view = new GroupUserView({
|
||||
model: user
|
||||
});
|
||||
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
}
|
||||
|
||||
});
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
const Backbone = require('backbone');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const PagedSearchView = require('dashboard/components/paged-search/paged-search-view');
|
||||
const PagedSearchModel = require('dashboard/data/paged-search-model');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
const loadingTemplate = require('builder/components/loading/loading.tpl');
|
||||
const randomQuote = require('builder/components/loading/random-quote');
|
||||
const AddRemoveFiltersExtraView = require('dashboard/views/organization/groups-admin/filters/add-or-remove-group-users-filters-extra-view');
|
||||
const EmptyGroupFiltersExtraView = require('dashboard/views/organization/groups-admin/filters/empty-group-filters-extra-view');
|
||||
const GroupUsersListView = require('dashboard/views/organization/groups-admin/group-users-list/group-users-list-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'group',
|
||||
'orgUsers',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* View to manage users of a group
|
||||
* It basically has two states, each which relies on its own collection:
|
||||
* - Empty group: i.e. no users, show organization users and allow to add users directly by selecting
|
||||
* - Group users: allow to add or remove users from group.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._groupUsers = this._group.users;
|
||||
this._hasPrefetchedGroupUsers = false;
|
||||
this._orgUsers.excludeCurrentUser(false);
|
||||
|
||||
this.model = new Backbone.Model({
|
||||
hasPrefetchedGroupUsers: false,
|
||||
lastRendered: null //, 'groupUsers', 'empty'
|
||||
});
|
||||
|
||||
// Init binds
|
||||
this.listenTo(this._groupUsers, 'sync', this._onResetGroupUsers);
|
||||
|
||||
// Pre-fetch to know what view to render
|
||||
this._groupUsers.fetch({
|
||||
success: () => {
|
||||
this.model.set('hasPrefetchedGroupUsers', true);
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
let view;
|
||||
if (this.model.get('hasPrefetchedGroupUsers')) {
|
||||
view = this._groupUsers.totalCount() > 0
|
||||
? this._createViewForGroupUsers()
|
||||
: this._createViewForEmptyGroup();
|
||||
} else {
|
||||
view = this._createInitialPreloadingView();
|
||||
}
|
||||
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._orgUsers.restoreExcludeCurrentUser();
|
||||
CoreView.prototype.clean.apply(this);
|
||||
},
|
||||
|
||||
_createInitialPreloadingView: function () {
|
||||
return ViewFactory.createByTemplate(loadingTemplate, {
|
||||
title: 'Getting users',
|
||||
descHTML: randomQuote()
|
||||
});
|
||||
},
|
||||
|
||||
_createViewForGroupUsers: function () {
|
||||
this.model.set('lastRendered', 'groupUsers');
|
||||
|
||||
const filtersExtrasView = new AddRemoveFiltersExtraView({
|
||||
group: this._group,
|
||||
orgUsers: this._orgUsers,
|
||||
userModel: this._userModel
|
||||
});
|
||||
this.addView(filtersExtrasView);
|
||||
|
||||
return new PagedSearchView({
|
||||
pagedSearchModel: new PagedSearchModel(),
|
||||
collection: this._groupUsers,
|
||||
createListView: this._createGroupUsersListView.bind(this, this._groupUsers),
|
||||
thinFilters: true,
|
||||
filtersExtrasView
|
||||
});
|
||||
},
|
||||
|
||||
_createViewForEmptyGroup: function () {
|
||||
this.model.set('lastRendered', 'empty');
|
||||
|
||||
const filtersExtrasView = new EmptyGroupFiltersExtraView({
|
||||
groupUsers: this._groupUsers,
|
||||
orgUsers: this._orgUsers,
|
||||
userModel: this._userModel
|
||||
});
|
||||
this.addView(filtersExtrasView);
|
||||
|
||||
return new PagedSearchView({
|
||||
pagedSearchModel: new PagedSearchModel(),
|
||||
collection: this._orgUsers,
|
||||
createListView: this._createGroupUsersListView.bind(this, this._orgUsers),
|
||||
thinFilters: true,
|
||||
filtersExtrasView
|
||||
});
|
||||
},
|
||||
|
||||
_createGroupUsersListView: function (usersCollection) {
|
||||
return new GroupUsersListView({
|
||||
users: usersCollection
|
||||
});
|
||||
},
|
||||
|
||||
_onResetGroupUsers: function () {
|
||||
// just this.render() is not enough, because each sub-view re-renders its view on state changes,
|
||||
// Instead, only re-render when hitting the edge-cases after a rest
|
||||
const lastRendered = this.model.get('lastRendered');
|
||||
const totalGroupUsersCount = this._groupUsers.totalCount();
|
||||
|
||||
if (lastRendered === 'empty') {
|
||||
// scenario: added at least one user, so group is no longer empty => change to group users view to add users
|
||||
if (totalGroupUsersCount > 0) {
|
||||
this.render();
|
||||
}
|
||||
} else if (lastRendered === 'groupUsers') {
|
||||
// scenario: removed last group user(s), so the group is now "empty" => change to org users view to add users
|
||||
if (totalGroupUsersCount === 0) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const template = require('./group.tpl');
|
||||
const pluralizeStr = require('dashboard/helpers/pluralize');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'model',
|
||||
'url'
|
||||
];
|
||||
|
||||
/**
|
||||
* View for an individual group.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
tagName: 'li',
|
||||
className: 'OrganizationList-user',
|
||||
_PREVIEW_COUNT: 3,
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
const sharedMapsCount = this.model.get('shared_maps_count');
|
||||
const sharedDatasetsCount = this.model.get('shared_tables_count');
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
displayName: this.model.get('display_name'),
|
||||
sharedMapsCount: pluralizeStr('1 shared map', `${sharedMapsCount} shared maps`, sharedMapsCount),
|
||||
sharedDatasetsCount: pluralizeStr('1 shared dataset', `${sharedDatasetsCount} shared datasets`, sharedDatasetsCount),
|
||||
url: this._url,
|
||||
previewUsers: this.model.users.toArray().slice(0, this._PREVIEW_COUNT),
|
||||
usersCount: Math.max(this.model.users.length - this._PREVIEW_COUNT, 0)
|
||||
})
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
<a class="CDB-Text OrganizationList-userLink" href="<%- url %>">
|
||||
<div class="OrganizationList-userInfo">
|
||||
<div class="OrganizationList-userInfoName">
|
||||
<h3 class="CDB-Size-medium OrganizationList-userInfoTitle u-ellipsLongText" title="<%- displayName %>">
|
||||
<%- displayName %>
|
||||
</h3>
|
||||
<h4 class="OrganizationList-userInfoSubtitle u-ellipsLongText">
|
||||
<%- sharedMapsCount %> • <%- sharedDatasetsCount %>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="OrganizationList-userInfoData">
|
||||
<% previewUsers.forEach(function(u) { %>
|
||||
<span class="UserAvatar is-in-list">
|
||||
<img class="UserAvatar-img UserAvatar-img--medium" src="<%- u.get('avatar_url') %>" title="<%- u.nameOrUsername() %>" />
|
||||
</span>
|
||||
<% }) %>
|
||||
<% if (usersCount > 0) { %>
|
||||
<span class="UserAvatar is-in-list">
|
||||
<span class="UserAvatar-img UserAvatar-img--medium UserAvatar-img--textReplacement">
|
||||
+<%- usersCount %>
|
||||
</span>
|
||||
</span>
|
||||
<% } %>
|
||||
</div>
|
||||
</a>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const GroupView = require('dashboard/views/organization/groups-admin/group-view/group-view');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'groups',
|
||||
'newGroupUrl'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this._renderGroupsView();
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderGroupsView: function () {
|
||||
const view = ViewFactory.createListView(this._createGroupViews(), {
|
||||
tagName: 'ul',
|
||||
className: 'OrganizationList'
|
||||
});
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
},
|
||||
|
||||
_createGroupViews: function () {
|
||||
return this._groups.map(model => {
|
||||
return () => new GroupView({
|
||||
model,
|
||||
url: this._newGroupUrl(model)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const navigateThroughRouter = require('builder/helpers/navigate-through-router');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'routerModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Controller view, managing view state of the groups entry point
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(this._contentView().render().el);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._routerModel.model, 'change:view', this._onChangeView);
|
||||
},
|
||||
|
||||
_onChangeView: function (model) {
|
||||
model.previous('view').clean();
|
||||
this.render();
|
||||
},
|
||||
|
||||
_contentView: function () {
|
||||
return this._routerModel.model.get('view');
|
||||
},
|
||||
|
||||
_onClick: function (event) {
|
||||
if (!this._isEventTriggeredOutsideOf(event, 'a')) {
|
||||
const url = $(event.target).closest('a').attr('href');
|
||||
if (this._routerModel.isWithinCurrentRoutes(url)) {
|
||||
navigateThroughRouter.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_isEventTriggeredOutsideOf: function (ev, selector) {
|
||||
return $(ev.target).closest(selector).length === 0;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
const Backbone = require('backbone');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
const loadingTemplate = require('builder/components/loading/loading.tpl');
|
||||
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
|
||||
const randomQuote = require('builder/components/loading/random-quote');
|
||||
|
||||
/**
|
||||
* Model representing the router state
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
view: ''
|
||||
},
|
||||
|
||||
createGroupView: function (groups, id, fetchedCallback) {
|
||||
const group = groups.newGroupById(id);
|
||||
fetchedCallback = fetchedCallback.bind(this, group);
|
||||
|
||||
const setFetchedCallbackView = () => {
|
||||
this.set('view', fetchedCallback());
|
||||
};
|
||||
|
||||
if (group.get('display_name')) {
|
||||
setFetchedCallbackView();
|
||||
} else {
|
||||
// No display name == model not fetched yet, so show loading msg meanwhile
|
||||
this.createLoadingView('Loading group details');
|
||||
|
||||
group.fetch({
|
||||
data: {
|
||||
fetch_users: true
|
||||
},
|
||||
success: function () {
|
||||
groups.add(group);
|
||||
setFetchedCallbackView();
|
||||
},
|
||||
error: this.createErrorView.bind(this)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
createLoadingView: function (msg) {
|
||||
this.set('view', ViewFactory.createByTemplate(loadingTemplate, {
|
||||
title: msg,
|
||||
descHTML: randomQuote()
|
||||
}));
|
||||
},
|
||||
|
||||
createErrorView: function () {
|
||||
// Generic error view for now
|
||||
this.set('view', ViewFactory.createByTemplate(errorTemplate, {
|
||||
msg: ''
|
||||
}));
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
const Backbone = require('backbone');
|
||||
|
||||
/**
|
||||
* Header view model to handle state for dashboard header view.
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
breadcrumbTitle: () => 'Configuration',
|
||||
|
||||
isBreadcrumbDropdownEnabled: () => false,
|
||||
|
||||
isDisplayingDatasets: () => false,
|
||||
|
||||
isDisplayingMaps: () => false,
|
||||
|
||||
isDisplayingLockedItems: () => false
|
||||
});
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<div class="js-dialogIconPicker">
|
||||
</div>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const OrganizationIconsView = require('../icons/organization-icons-view');
|
||||
const template = require('./icon-picker-dialog.tpl');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'orgId',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Dialog Modal IconPickerDialog',
|
||||
|
||||
events: {
|
||||
'click .js-addIcon': '_onAddIconClicked'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template());
|
||||
this.$('.content').addClass('Dialog-content--expanded');
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this.icon_picker = new OrganizationIconsView({
|
||||
el: this.$('.js-dialogIconPicker'),
|
||||
orgId: this._orgId,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this.addView(this.icon_picker);
|
||||
|
||||
this.icon_picker.model.on('change:isProcessRunning', this._onIsProcessRunningChanged, this);
|
||||
},
|
||||
|
||||
_onIsProcessRunningChanged: function () {
|
||||
var running = this.icon_picker.model.get('isProcessRunning');
|
||||
if (running) {
|
||||
this.$el.css('pointer-events', 'none');
|
||||
} else {
|
||||
this.$el.css('pointer-events', 'auto');
|
||||
}
|
||||
},
|
||||
|
||||
_onAddIconClicked: function (event) {
|
||||
this.killEvent(event);
|
||||
|
||||
this._hideErrorMessage();
|
||||
this.$('.js-inputFile').trigger('click');
|
||||
},
|
||||
|
||||
_hideErrorMessage: function () {
|
||||
this._hide('.js-errorMessage');
|
||||
},
|
||||
|
||||
_hide: function (selector) {
|
||||
this.$(selector).addClass('is-hidden');
|
||||
}
|
||||
});
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-headerContainer">
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-bSpace">Organization icons</h2>
|
||||
<h3 class="CDB-Text CDB-Size-medium u-altTextColor"><a data-type="upload_file" href="#/upload" class="js-addIcon">Upload organization icons</a></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Modal-container IconModal">
|
||||
<div class="Tab-pane">
|
||||
<div class="Modal-inner">
|
||||
<div class="Dialog-body Dialog-body--expanded Dialog-body--create Dialog-body--withoutBorder">
|
||||
<div class="u-inner">
|
||||
<div class="js-dialogIconPicker"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<li class="CDB-NavMenu-item is-disabled js-your-icons<%- pane === 'your_icons' ? ' is-selected' : '' %>">
|
||||
<button data-type="your_icons" class="CDB-NavMenu-link js-item u-upperCase">Organization icons</button>
|
||||
</li>
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const template = require('./delete-icons-dialog.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'modalModel',
|
||||
'onSubmit'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-submit': '_onSubmitClicked',
|
||||
'click .js-cancel': '_closeDialog'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._numOfIcons = this.options.numOfIcons || 0;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template({
|
||||
numOfIcons: this._numOfIcons
|
||||
}));
|
||||
},
|
||||
|
||||
_onSubmitClicked: function () {
|
||||
this._onSubmit();
|
||||
this._closeDialog();
|
||||
},
|
||||
|
||||
_closeDialog: function () {
|
||||
this._modalModel.destroy();
|
||||
}
|
||||
});
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<div class="CDB-Text Dialog-header u-inner">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--negative">
|
||||
<i class="CDB-IconFont CDB-IconFont-trash"></i>
|
||||
<% if (numOfIcons > 0) { %>
|
||||
<span class="Badge Badge--negative Dialog-headerIconBadge CDB-Text CDB-Size-small"><%- numOfIcons %></span>
|
||||
<% } %>
|
||||
</div>
|
||||
<h4 class="CDB-Text CDB-Size-large u-mainTextColor u-secondaryTextColor u-bSpace--m u-tSpace-xl">
|
||||
You are deleting
|
||||
<% if (numOfIcons <= 0) { %>
|
||||
icons
|
||||
<% } else if (numOfIcons === 1) { %>
|
||||
1 icon
|
||||
<% } else { %>
|
||||
<%- numOfIcons %> icons
|
||||
<% } %>
|
||||
from your organization.
|
||||
</h4>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">
|
||||
Once icons are deleted they cannot be recovered. Any maps within the organization that use
|
||||
<% if (numOfIcons === 1) { %>
|
||||
this icon
|
||||
<% } else { %>
|
||||
these icons
|
||||
<% } %>
|
||||
will be adversely affected.
|
||||
</p>
|
||||
<p class="CDB-Text CDB-Size-medium u-altTextColor">Please be sure before proceeding.</p>
|
||||
</div>
|
||||
|
||||
<div class="Dialog-footer Dialog-footer--simple u-inner">
|
||||
<div class="Dialog-footerContent">
|
||||
<button class="CDB-Button CDB-Button--secondary js-cancel">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Cancel</span>
|
||||
</button>
|
||||
<button class="u-lSpace--xl CDB-Button CDB-Button--error js-submit">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Ok, delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
const Backbone = require('backbone');
|
||||
const IconModel = require('./organization-icon-model');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'orgId',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
initialize: function (models, options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
model: function (attrs, opts) {
|
||||
const options = { ...opts, configModel: opts.collection._configModel };
|
||||
|
||||
return new IconModel(attrs, options);
|
||||
},
|
||||
|
||||
url: function (method) {
|
||||
const version = this._configModel.urlVersion('organization-assets', method);
|
||||
|
||||
return `/api/${version}/organization/${this._orgId}/assets`;
|
||||
}
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
const Backbone = require('backbone');
|
||||
const _ = require('underscore');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
selected: false,
|
||||
visible: false,
|
||||
deleted: false
|
||||
},
|
||||
|
||||
fileAttribute: 'resource',
|
||||
|
||||
initialize: function (models, options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
save: function (attrs, options = {}) {
|
||||
attrs = attrs || _.clone(this.attributes);
|
||||
|
||||
attrs = _.omit(attrs, ['selected', 'visible', 'deleted']);
|
||||
options.data = JSON.stringify(attrs);
|
||||
|
||||
return Backbone.Model.prototype.save.call(this, attrs, options);
|
||||
}
|
||||
});
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
const _ = require('underscore');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const template = require('./organization-icon.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
tagName: 'li',
|
||||
|
||||
className: 'IconList-item IconList-item--small',
|
||||
|
||||
events: {
|
||||
'click': '_onClick'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
if (_.isUndefined(options.model)) {
|
||||
throw new Error('An organization icon model is mandatory.');
|
||||
}
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template({
|
||||
url: this.model.get('public_url')
|
||||
}));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:selected', this._onSelectedChanged);
|
||||
this.listenTo(this.model, 'change:deleted', this._onDeletedChanged);
|
||||
},
|
||||
|
||||
_onClick: function () {
|
||||
this.model.set('selected', !this.model.get('selected'));
|
||||
},
|
||||
|
||||
_onSelectedChanged: function () {
|
||||
this.$el.toggleClass('is-selected', this.model.get('selected'));
|
||||
},
|
||||
|
||||
_onDeletedChanged: function () {
|
||||
if (this.model.get('deleted')) {
|
||||
this.$el.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<div class="IconItem-icon js-asset">
|
||||
<img height="24" src="<%- url %>" alt="icon" crossorigin="anonymous" />
|
||||
</div>
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
const Backbone = require('backbone');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const _ = require('underscore');
|
||||
const IconCollection = require('./organization-icon-collection');
|
||||
const IconView = require('./organization-icon-view');
|
||||
const DeleteIconsDialog = require('./delete-icons-dialog-view');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const template = require('./organization-icons.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'orgId',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-addIcon': '_onAddIconClicked',
|
||||
'click .js-selectAllIcons': '_onSelectAllIconsClicked',
|
||||
'click .js-deselectAllIcons': '_onDeselectAllIconsClicked',
|
||||
'click .js-deleteIcons': '_onDeleteIconsClicked',
|
||||
'change .js-inputFile': '_onFileSelected'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._modals = new ModalsServiceModel();
|
||||
this.model = new Backbone.Model({
|
||||
isProcessRunning: false
|
||||
});
|
||||
this._iconCollection = new IconCollection(null, {
|
||||
orgId: this._orgId,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this._numOfUploadingProcesses = 0;
|
||||
this._numOfDeletingProcesses = 0;
|
||||
this._fetchErrorMessage = 'Error fetching organization icons. Please refresh the page.';
|
||||
this._runningMessage = '';
|
||||
|
||||
this.render();
|
||||
this._fetchAllIcons();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template());
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._iconCollection, 'change:selected', this._refreshActions);
|
||||
this.listenTo(this.model, 'change:isProcessRunning', this._onProcessRunningChanged);
|
||||
},
|
||||
|
||||
_fetchAllIcons: function () {
|
||||
this._iconCollection.fetch({
|
||||
success: this._renderAllIcons.bind(this),
|
||||
error: this._onFetchIconsError.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
_renderAllIcons: function () {
|
||||
_.each(this._iconCollection.models, this._addIconElement, this);
|
||||
},
|
||||
|
||||
_onFetchIconsError: function () {
|
||||
this._showErrorMessage(this._fetchErrorMessage);
|
||||
},
|
||||
|
||||
_renderIcon: function (iconModel) {
|
||||
var iconView = new IconView({
|
||||
model: iconModel
|
||||
});
|
||||
iconView.render();
|
||||
iconModel.set('visible', true);
|
||||
this.$('.js-items').append(iconView.$el);
|
||||
},
|
||||
|
||||
_addIconElement: function (iconModel) {
|
||||
this._renderIcon(iconModel);
|
||||
},
|
||||
|
||||
_onAddIconClicked: function (event) {
|
||||
this.killEvent(event);
|
||||
|
||||
this._hideErrorMessage();
|
||||
this.$('.js-inputFile').trigger('click');
|
||||
},
|
||||
|
||||
_parseResponseText: function (response) {
|
||||
if (response && response.responseText) {
|
||||
try {
|
||||
var text = JSON.parse(response.responseText);
|
||||
if (text && text.errors && typeof text.errors === 'string') {
|
||||
return text.errors;
|
||||
}
|
||||
} catch (exc) {
|
||||
// Swallow
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
_getSelectedFiles: function () {
|
||||
return this.$('.js-inputFile').prop('files');
|
||||
},
|
||||
|
||||
_onFileSelected: function () {
|
||||
var files = this._getSelectedFiles();
|
||||
|
||||
_.each(files, function (file) {
|
||||
this._iconCollection.create({
|
||||
kind: 'organization_asset',
|
||||
resource: file
|
||||
}, {
|
||||
beforeSend: this._beforeIconUpload.bind(this),
|
||||
success: this._onIconUploaded.bind(this),
|
||||
error: this._onIconUploadError.bind(this),
|
||||
complete: this._onIconUploadComplete.bind(this)
|
||||
});
|
||||
}, this);
|
||||
},
|
||||
|
||||
_beforeIconUpload: function () {
|
||||
this._numOfUploadingProcesses++;
|
||||
if (this._numOfUploadingProcesses > 0) {
|
||||
this._runningMessage = 'Uploading icons...';
|
||||
this.model.set('isProcessRunning', true);
|
||||
}
|
||||
},
|
||||
|
||||
_onIconUploaded: function (iconModel) {
|
||||
this._resetFileSelection();
|
||||
this._addIconElement(iconModel);
|
||||
},
|
||||
|
||||
_onIconUploadError: function (model, response) {
|
||||
var errorText = this._parseResponseText(response);
|
||||
this._resetFileSelection();
|
||||
this._showErrorMessage(this._uploadErrorMessage(errorText));
|
||||
},
|
||||
|
||||
_onIconUploadComplete: function () {
|
||||
this._numOfUploadingProcesses--;
|
||||
if (this._numOfUploadingProcesses <= 0) {
|
||||
this.model.set('isProcessRunning', false);
|
||||
}
|
||||
},
|
||||
|
||||
_resetFileSelection: function () {
|
||||
this.$('.js-inputFile').val('');
|
||||
},
|
||||
|
||||
_show: function (selector) {
|
||||
this.$(selector).removeClass('is-hidden');
|
||||
},
|
||||
|
||||
_hide: function (selector) {
|
||||
this.$(selector).addClass('is-hidden');
|
||||
},
|
||||
|
||||
_refreshActions: function () {
|
||||
if (this.model.get('isProcessRunning')) {
|
||||
return;
|
||||
}
|
||||
var limit = Math.min(this._iconCollection.length);
|
||||
var numOfSelectedIcons = this._getNumberOfSelectedIcons();
|
||||
var iconText = (numOfSelectedIcons === 1 ? '1 icon selected' : '' + numOfSelectedIcons + ' icons selected');
|
||||
this.$('.js-iconMainLabel').text(iconText);
|
||||
|
||||
if (numOfSelectedIcons === 0) {
|
||||
this.$('.js-iconMainLabel').text('');
|
||||
this._hide('.js-iconMainLabel, .js-selectAllIcons, .js-deselectAllIcons, .js-deleteIcons');
|
||||
this._show('.js-iconsInfo');
|
||||
} else if (numOfSelectedIcons < limit) {
|
||||
this._show('.js-iconMainLabel, .js-selectAllIcons, .js-deleteIcons');
|
||||
this._hide('.js-deselectAllIcons, .js-iconsInfo');
|
||||
} else {
|
||||
this._show('.js-iconMainLabel, .js-deselectAllIcons, .js-deleteIcons');
|
||||
this._hide('.js-selectAllIcons, .js-iconsInfo');
|
||||
}
|
||||
|
||||
if (numOfSelectedIcons > 1) {
|
||||
this.$('.js-deleteIcons a').text('Delete icons...');
|
||||
} else if (numOfSelectedIcons === 1) {
|
||||
this.$('.js-deleteIcons a').text('Delete icon...');
|
||||
}
|
||||
},
|
||||
|
||||
_hideActions: function () {
|
||||
this._hide('.js-selectAllIcons, .js-deselectAllIcons, .js-deleteIcons, .js-iconsInfo');
|
||||
},
|
||||
|
||||
_onDeselectAllIconsClicked: function (event) {
|
||||
event.preventDefault();
|
||||
this._iconCollection.each(function (icon) {
|
||||
icon.set('selected', false);
|
||||
});
|
||||
},
|
||||
|
||||
_onSelectAllIconsClicked: function (event) {
|
||||
event.preventDefault();
|
||||
this._iconCollection.each(function (icon) {
|
||||
if (icon.get('visible')) {
|
||||
icon.set('selected', true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onDeleteIconsClicked: function (event) {
|
||||
event.preventDefault();
|
||||
this._openDeleteIconsDialog();
|
||||
},
|
||||
|
||||
_openDeleteIconsDialog: function (event) {
|
||||
this.killEvent(event);
|
||||
|
||||
this._modals.create(modalModel =>
|
||||
new DeleteIconsDialog({
|
||||
modalModel,
|
||||
numOfIcons: this._getNumberOfSelectedIcons(),
|
||||
onSubmit: this._deleteIcons.bind(this)
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_deleteIcons: function () {
|
||||
this._hideErrorMessage();
|
||||
var iconsToDelete = this._iconCollection.where({ selected: true });
|
||||
|
||||
_.each(iconsToDelete, function (icon) {
|
||||
icon.destroy({
|
||||
beforeSend: this._beforeIconDelete.bind(this),
|
||||
success: this._onIconDeleted.bind(this),
|
||||
error: this._onIconDeleteError.bind(this),
|
||||
complete: this._onIconDeleteComplete.bind(this)
|
||||
});
|
||||
}, this);
|
||||
},
|
||||
|
||||
_getNumberOfSelectedIcons: function () {
|
||||
return this._iconCollection.where({ selected: true }).length;
|
||||
},
|
||||
|
||||
_beforeIconDelete: function () {
|
||||
this._numOfDeletingProcesses++;
|
||||
if (this._numOfDeletingProcesses > 0) {
|
||||
this._runningMessage = 'Deleting icons...';
|
||||
this.model.set('isProcessRunning', true);
|
||||
}
|
||||
},
|
||||
|
||||
_onIconDeleted: function (icon) {
|
||||
icon.set('deleted', true);
|
||||
this._addExtraIcon();
|
||||
},
|
||||
|
||||
_onIconDeleteError: function (icon, response) {
|
||||
var errorText = this._parseResponseText(response);
|
||||
// Even if API throws error the icon has been already removed from the collection.
|
||||
// We must add it again
|
||||
this._iconCollection.add(icon);
|
||||
this._resetSelection();
|
||||
this._showErrorMessage(this._deleteErrorMessage(errorText));
|
||||
},
|
||||
|
||||
_onIconDeleteComplete: function () {
|
||||
this._numOfDeletingProcesses--;
|
||||
if (this._numOfDeletingProcesses <= 0) {
|
||||
this.model.set('isProcessRunning', false);
|
||||
}
|
||||
},
|
||||
|
||||
_showSpinner: function (enable) {
|
||||
if (enable) {
|
||||
this._hide('.js-plusSign');
|
||||
this._show('.js-spinner');
|
||||
} else {
|
||||
this._show('.js-plusSign');
|
||||
this._hide('.js-spinner');
|
||||
}
|
||||
},
|
||||
|
||||
_showErrorMessage: function (message) {
|
||||
this.$('.js-errorMessage label').text(message);
|
||||
this._show('.js-errorMessage');
|
||||
},
|
||||
|
||||
_hideErrorMessage: function () {
|
||||
this._hide('.js-errorMessage');
|
||||
},
|
||||
|
||||
_resetSelection: function () {
|
||||
this._iconCollection.each(function (icon) {
|
||||
icon.set('selected', false);
|
||||
});
|
||||
this._refreshActions();
|
||||
},
|
||||
|
||||
_addExtraIcon: function () {
|
||||
var iconAdded = false;
|
||||
this._iconCollection.each(function (icon) {
|
||||
if (!icon.get('visible') && !iconAdded) {
|
||||
this._addIconElement(icon);
|
||||
iconAdded = true;
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
_onProcessRunningChanged: function () {
|
||||
var running = this.model.get('isProcessRunning');
|
||||
this._showSpinner(running);
|
||||
if (running) {
|
||||
this.$el.css('pointer-events', 'none');
|
||||
this._hideActions();
|
||||
this.$('.js-runningInfo').text(this._runningMessage);
|
||||
this._show('.js-runningInfo');
|
||||
} else {
|
||||
this.$('.js-runningInfo').text('');
|
||||
this._hide('.js-runningInfo');
|
||||
this._refreshActions();
|
||||
this._iconCollection.trigger('refreshCollection', { cid: this.cid });
|
||||
this.$el.css('pointer-events', 'auto');
|
||||
}
|
||||
},
|
||||
|
||||
_uploadErrorMessage: function (errorText) {
|
||||
var message = 'Error uploading your image. ';
|
||||
if (errorText) {
|
||||
message += '[ ' + errorText + ' ]. ';
|
||||
}
|
||||
message += 'Please try again.';
|
||||
|
||||
return message;
|
||||
},
|
||||
|
||||
_deleteErrorMessage: function (errorText) {
|
||||
var message = 'Error deleting your image. ';
|
||||
if (errorText) {
|
||||
message += '[ ' + errorText + ' ]. ';
|
||||
}
|
||||
message += 'Please try again.';
|
||||
|
||||
return message;
|
||||
}
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<div class="FormAccount-rowLabel IconActions">
|
||||
<label class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor u-rSpace--xl js-iconMainLabel is-hidden"></label>
|
||||
<label class="CDB-Text CDB-Size-small u-rSpace--xl u-altTextColor js-iconsInfo">These icons can be used as markers.</label>
|
||||
<label class="CDB-Text CDB-Size-small u-rSpace--xl u-altTextColor js-runningInfo is-hidden"></label>
|
||||
<label class="CDB-Text CDB-Size-medium u-rSpace--xl js-selectAllIcons is-hidden"><a href="#/select_all">Select all</a></label>
|
||||
<label class="CDB-Text CDB-Size-medium u-rSpace--xl js-deselectAllIcons is-hidden"><a href="#/deselect_all">Deselect all</a></label>
|
||||
<label class="CDB-Text CDB-Size-medium js-deleteIcons is-hidden"><a class="IconText is--critical" href="#/delete_icons">Delete icons...</a></label>
|
||||
</div>
|
||||
<div class="FormAccount-rowData js-asset-icons">
|
||||
<ul class="IconList js-items">
|
||||
|
||||
<li class="IconList-item IconList-item--small IconList-item--dashed js-addIcon">
|
||||
<div class="IconItem-icon js-plusSign">
|
||||
<svg width="11px" height="11px" viewBox="15 15 11 11" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<polygon id="Mas" stroke="none" fill="#1785FB" fill-rule="evenodd" points="15 20 15 21 20 21 20 26 21 26 21 21 26 21 26 20 21 20 21 15 20 15 20 20"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="CDB-LoaderIcon is-blue js-spinner is-hidden">
|
||||
<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>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div class="FormAccount-rowData js-errorMessage is-hidden">
|
||||
<label class="CDB-Text CDB-Size-medium IconText is--critical"></label>
|
||||
</div>
|
||||
<input id="iconfile" class="js-inputFile" type="file" value="Choose icon" accept="image/jpeg,image/jpg,image/gif,image/png,image/svg+xml" tabindex="-1" style="position: absolute; clip: rect(0px 0px 0px 0px); opacity: 0;" multiple>
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
const Backbone = require('backbone');
|
||||
const IconPickerView = require('./icons/organization-icons-view');
|
||||
const OrganizationIconCollection = require('./icons/organization-icon-collection');
|
||||
const IconView = require('./icons/organization-icon-view');
|
||||
const IconPickerDialog = require('./icon-picker-dialog/icon-picker-dialog-view');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const template = require('./organization-icon-picker.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'orgId',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = IconPickerView.extend({
|
||||
|
||||
events: IconPickerView.extendEvents({
|
||||
'click .js-viewAllIcons': '_onViewAllIconsClicked'
|
||||
}),
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._maxIcons = 23;
|
||||
this._modals = new ModalsServiceModel();
|
||||
this.model = new Backbone.Model({
|
||||
isProcessRunning: false
|
||||
});
|
||||
this._iconCollection = new OrganizationIconCollection(null, {
|
||||
orgId: this._orgId,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this._numOfUploadingProcesses = 0;
|
||||
this._numOfDeletingProcesses = 0;
|
||||
this._fetchErrorMessage = 'Error fetching organization icons. Please refresh the page.';
|
||||
this._runningMessage = '';
|
||||
this.teplate = template;
|
||||
|
||||
this.render();
|
||||
this._fetchAllIcons();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(template());
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._iconCollection, 'change:selected', this._refreshActions);
|
||||
this.listenTo(this._iconCollection, 'refreshCollection', this._refreshCollection);
|
||||
this.listenTo(this.model, 'change:isProcessRunning', this._onProcessRunningChanged);
|
||||
},
|
||||
|
||||
_refreshCollection: function (data) {
|
||||
if (data.cid && this.cid !== data.cid) {
|
||||
this.render();
|
||||
this._fetchAllIcons();
|
||||
}
|
||||
},
|
||||
|
||||
_renderIcon: function (iconModel) {
|
||||
if (iconModel.get('index') < this._maxIcons) {
|
||||
var iconView = new IconView({
|
||||
model: iconModel
|
||||
});
|
||||
iconView.render();
|
||||
iconModel.set('visible', true);
|
||||
this.$('.js-items').append(iconView.$el);
|
||||
}
|
||||
},
|
||||
|
||||
_addIconElement: function (iconModel) {
|
||||
iconModel.set('index', this._getIconIndex(iconModel));
|
||||
this._renderIcon(iconModel);
|
||||
this._refreshActions();
|
||||
},
|
||||
|
||||
_refreshActions: function () {
|
||||
if (this.model.get('isProcessRunning')) {
|
||||
return;
|
||||
}
|
||||
var limit = Math.min(this._maxIcons, this._iconCollection.length);
|
||||
var numOfSelectedIcons = this._getNumberOfSelectedIcons();
|
||||
var iconText = (numOfSelectedIcons === 1 ? '1 icon selected' : '' + numOfSelectedIcons + ' icons selected');
|
||||
this.$('.js-iconMainLabel').text(iconText);
|
||||
|
||||
if (numOfSelectedIcons === 0) {
|
||||
this.$('.js-iconMainLabel').text('Icons');
|
||||
this._hide('.js-selectAllIcons, .js-deselectAllIcons, .js-deleteIcons');
|
||||
this._show('.js-iconsInfo');
|
||||
} else if (numOfSelectedIcons < limit) {
|
||||
this._show('.js-selectAllIcons, .js-deleteIcons');
|
||||
this._hide('.js-deselectAllIcons, .js-iconsInfo');
|
||||
} else {
|
||||
this._hide('.js-selectAllIcons, .js-iconsInfo');
|
||||
this._show('.js-deselectAllIcons, .js-deleteIcons');
|
||||
}
|
||||
|
||||
if (numOfSelectedIcons > 1) {
|
||||
this.$('.js-deleteIcons a').text('Delete icons...');
|
||||
} else if (numOfSelectedIcons === 1) {
|
||||
this.$('.js-deleteIcons a').text('Delete icon...');
|
||||
}
|
||||
|
||||
if (this._iconCollection.length > this._maxIcons) {
|
||||
this._show('.js-viewAllIcons');
|
||||
} else {
|
||||
this._hide('.js-viewAllIcons');
|
||||
}
|
||||
},
|
||||
|
||||
_hideActions: function () {
|
||||
this._hide('.js-selectAllIcons, .js-deselectAllIcons, .js-deleteIcons, .js-iconsInfo, .js-viewAllIcons');
|
||||
},
|
||||
|
||||
_getIconIndex: function (icon) {
|
||||
return this._iconCollection.indexOf(icon);
|
||||
},
|
||||
|
||||
_addExtraIcon: function () {
|
||||
var iconAdded = false;
|
||||
this._iconCollection.each(function (icon) {
|
||||
var index = this._getIconIndex(icon);
|
||||
if (index < this._maxIcons && !icon.get('visible') && !iconAdded) {
|
||||
this._addIconElement(icon);
|
||||
iconAdded = true;
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
_onViewAllIconsClicked: function (event) {
|
||||
this.killEvent(event);
|
||||
|
||||
this._modals.create(modalModel =>
|
||||
new IconPickerDialog({
|
||||
orgId: this._orgId,
|
||||
configModel: this._configModel
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<div class="FormAccount-rowLabel">
|
||||
<label class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor u-rSpace--xl js-iconMainLabel">Icons</label>
|
||||
<label class="CDB-Text CDB-Size-medium u-rSpace--xl js-viewAllIcons is-hidden"><a href="#/view_icons">View all icons</a></label>
|
||||
<label class="CDB-Text CDB-Size-small u-rSpace--xl u-altTextColor js-iconsInfo">These icons can be used as markers.</label>
|
||||
<label class="CDB-Text CDB-Size-small u-rSpace--xl u-altTextColor js-runningInfo is-hidden"></label>
|
||||
<label class="CDB-Text CDB-Size-medium u-rSpace--xl js-selectAllIcons is-hidden"><a href="#/select_all">Select all</a></label>
|
||||
<label class="CDB-Text CDB-Size-medium u-rSpace--xl js-deselectAllIcons is-hidden"><a href="#/deselect_all">Deselect all</a></label>
|
||||
<label class="CDB-Text CDB-Size-medium js-deleteIcons is-hidden"><a class="IconText is--critical" href="#/delete_icons">Delete icons...</a></label>
|
||||
</div>
|
||||
<div class="FormAccount-rowData js-asset-icons">
|
||||
<ul class="IconList js-items">
|
||||
|
||||
<li class="IconList-item IconList-item--small IconList-item--dashed js-addIcon">
|
||||
<div class="IconItem-icon js-plusSign">
|
||||
<svg width="11px" height="11px" viewBox="15 15 11 11" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<polygon id="Mas" stroke="none" fill="#1785FB" fill-rule="evenodd" points="15 20 15 21 20 21 20 26 21 26 21 21 26 21 26 20 21 20 21 15 20 15 20 20"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="CDB-LoaderIcon is-blue js-spinner is-hidden">
|
||||
<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>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div class="FormAccount-rowData js-errorMessage is-hidden">
|
||||
<label class="CDB-Text CDB-Size-medium IconText is--critical"></label>
|
||||
</div>
|
||||
<input id="iconfile" class="js-inputFile" type="file" value="Choose icon" accept="image/jpeg,image/jpg,image/gif,image/png,image/svg+xml" tabindex="-1" style="position: absolute; clip: rect(0px 0px 0px 0px); opacity: 0;" multiple>
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const loadingView = require('builder/components/loading/render-loading');
|
||||
const OrganizationInviteModel = require('dashboard/data/organization-invite-model');
|
||||
const TabPane = require('dashboard/components/tabpane/tabpane');
|
||||
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
|
||||
const InviteUsersFormView = require('./invite-users-form-view');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
const template = require('./invite-users-dialog.tpl');
|
||||
|
||||
require('jquery-ui');
|
||||
require('tagit');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'modalModel',
|
||||
'organization',
|
||||
'organizationUsers',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Invite users dialog
|
||||
*
|
||||
* - Send invites via email.
|
||||
* - Shouldn't send emails to already enabled users.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'InviteUsers',
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this.model = new OrganizationInviteModel({}, {
|
||||
configModel: options.configModel,
|
||||
organizationId: this._organization.id,
|
||||
enable_organization_signup: false
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template());
|
||||
|
||||
this.$('.content').addClass('Dialog-content--expanded');
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
// Panes
|
||||
this._panes = new TabPane({
|
||||
el: this.$('.js-content')
|
||||
});
|
||||
|
||||
// Create form
|
||||
this._form = new InviteUsersFormView({
|
||||
model: this.model,
|
||||
organization: this._organization,
|
||||
organizationUsers: this._organizationUsers,
|
||||
el: this.$('.js-form')
|
||||
});
|
||||
|
||||
this._form.bind('onSubmit', this._sendInvites, this);
|
||||
|
||||
this._panes.addTab('form', this._form.render());
|
||||
|
||||
// Create loading
|
||||
this._panes.addTab('loading', ViewFactory.createByHTML(loadingView({
|
||||
title: 'Sending invites...'
|
||||
})).render());
|
||||
|
||||
// Create error
|
||||
this._panes.addTab('error', ViewFactory.createByHTML(errorTemplate({
|
||||
msg: 'Sorry, something went wrong.'
|
||||
})).render());
|
||||
|
||||
this._panes.active('form');
|
||||
},
|
||||
|
||||
_sendInvites: function () {
|
||||
this._panes.active('loading');
|
||||
|
||||
this.model.save(null, {
|
||||
success: () => this._modalModel.destroy(),
|
||||
error: (model, error) => {
|
||||
try {
|
||||
const message = JSON.parse(error.responseText).errors.users_emails[0];
|
||||
|
||||
this._form.showSubmitError(message);
|
||||
this._panes.active('form');
|
||||
} catch (e) {
|
||||
this._panes.active('error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<div class="CDB-Text Dialog-header Dialog-header--expanded">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--neutral">
|
||||
<i class="CDB-IconFont CDB-IconFont-boss"></i>
|
||||
</div>
|
||||
<h3 class="Dialog-headerTitle">Invite users to your Organization</h3>
|
||||
<p class="Dialog-headerText">New user will become part of your CARTO organization account.</p>
|
||||
</div>
|
||||
<div class="CDB-Text Dialog-expandedSubContent js-content">
|
||||
<div class="InviteUsers-form">
|
||||
<div class="js-form"></div>
|
||||
</div>
|
||||
</div>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<div class="FlashMessage js-signInMessage">
|
||||
<div class='u-inner'>
|
||||
<div class="FlashMessage-content js-flashNotice">
|
||||
<p class='FlashMessage-info FlashMessage-info--larger'>If you want to invite users you need to enable the sign in page for your organization.</p>
|
||||
<button class='FlashMessage--button js-enableSignInButton'><span>Enable sign in</span></button>
|
||||
</div>
|
||||
|
||||
<div class="FlashMessage-content js-flashSuccess">
|
||||
<p class='FlashMessage-info FlashMessage-info--larger'>You have enabled the sign in page successfully!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
const _ = require('underscore');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const Utils = require('builder/helpers/utils');
|
||||
const template = require('./invite-users-form.tpl');
|
||||
const flashTemplate = require('./invite-users-flash-message.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'organization',
|
||||
'organizationUsers'
|
||||
];
|
||||
|
||||
/**
|
||||
* Form view for invite users dialog
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'submit .js-invitesForm': '_onSubmit',
|
||||
'keyup .js-welcomeText': '_onTextareaChange',
|
||||
'click .js-enableSignInButton': '_signInEnabledMessage'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
welcomeText: this.model.get('welcome_text'),
|
||||
signupEnabled: this._organization.get('signup_enabled'),
|
||||
viewerEnabled: this._organization.get('viewer_seats') > 0
|
||||
})
|
||||
);
|
||||
|
||||
this._toggleEmailError(false);
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderFlashMessage: function () {
|
||||
this.$('.js-signInMessageContainer').html(flashTemplate());
|
||||
this.$('.js-flashSuccess').hide();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change', this._onChange);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
const organizationUsersEmail = this._organizationUsers.pluck('email');
|
||||
|
||||
this.$('.js-tagsList').tagit({
|
||||
allowSpaces: true,
|
||||
placeholderText: this.$('.js-tags').data('title'),
|
||||
onBlur: () => this.$('.js-tags').removeClass('is-focus'),
|
||||
onFocus: () => this.$('.js-tags').addClass('is-focus'),
|
||||
beforeTagAdded: (event, ui) => {
|
||||
var value = ui.tagLabel;
|
||||
this._removeSubmitError();
|
||||
|
||||
// It is an email
|
||||
if (!Utils.isValidEmail(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// It is already in the organization
|
||||
if (_.contains(organizationUsersEmail, value)) {
|
||||
this._toggleEmailError(true);
|
||||
return false;
|
||||
} else {
|
||||
this._toggleEmailError(false);
|
||||
}
|
||||
},
|
||||
beforeTagRemoved: () => this._removeSubmitError(),
|
||||
afterTagRemoved: () => this._updateUsers(),
|
||||
afterTagAdded: () => this._updateUsers(),
|
||||
onSubmitTags: (event) => {
|
||||
event.preventDefault();
|
||||
this._onSubmit();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!this._organization.get('signup_enabled')) {
|
||||
this._renderFlashMessage();
|
||||
}
|
||||
},
|
||||
|
||||
_onTextareaChange: function () {
|
||||
this.model.set('welcome_text', this.$('.js-welcomeText').val());
|
||||
},
|
||||
|
||||
_updateUsers: function () {
|
||||
this.model.set('users_emails', this.$('.js-tagsList').tagit('assignedTags'));
|
||||
},
|
||||
|
||||
_onSubmit: function (event) {
|
||||
event && this.killEvent(event);
|
||||
|
||||
const emails = this.model.get('users_emails');
|
||||
|
||||
if (emails.length > 0) {
|
||||
this.model.set('welcome_text', this.$('.js-welcomeText').val());
|
||||
this.model.set('viewer', this.$('[name=viewer]').get(0).checked);
|
||||
this.trigger('onSubmit', this);
|
||||
}
|
||||
},
|
||||
|
||||
_signInEnabledMessage: function () {
|
||||
this.$('.js-signInMessage').addClass('FlashMessage--success');
|
||||
this.$('.js-flashNotice').hide();
|
||||
this.$('.js-flashSuccess').show();
|
||||
|
||||
this.model.set('enable_organization_signup', true);
|
||||
},
|
||||
|
||||
_toggleEmailError: function (visible) {
|
||||
this.$('.js-emailError')[ visible ? 'show' : 'hide' ]();
|
||||
},
|
||||
|
||||
showSubmitError: function (msg) {
|
||||
const $serverError = this.$('.js-serverError');
|
||||
|
||||
if ($serverError.length === 0) {
|
||||
this.$('.js-emailError').after('<p class="Form-rowInfoText Form-rowInfoText--error Form-rowInfoText--multipleLines js-serverError">' + msg + '</p>');
|
||||
}
|
||||
},
|
||||
|
||||
_removeSubmitError: function () {
|
||||
this.$('.js-serverError').remove();
|
||||
},
|
||||
|
||||
_onChange: function () {
|
||||
const users = this.model.get('users_emails');
|
||||
const welcomeText = this.model.get('welcome_text');
|
||||
|
||||
this.$('.js-submit').toggleClass('is-disabled', users.length === 0 || !welcomeText);
|
||||
}
|
||||
});
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<div class="js-signInMessageContainer"></div>
|
||||
|
||||
<form class="Form InviteUsers-formContent js-invitesForm">
|
||||
<div class="Form-row Form-row--centered has-label">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="Form-label">Users email</label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<div class="Form-tags js-tags" data-title="Separate emails by commas">
|
||||
<ul class="Form-tagsList js-tagsList"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Form-rowInfo">
|
||||
<p class="Form-rowInfoText Form-rowInfoText--error Form-rowInfoText--multipleLines js-emailError">That email is already used in this organization</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Form-row Form-row--centered has-label">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="Form-label">Welcome text</label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer">
|
||||
<textarea class="CDB-Textarea Form-input Form-input--longer Form-textarea js-welcomeText" placeholder="Send them a welcome text"><%- welcomeText %></textarea>
|
||||
</div>
|
||||
<div class="Form-rowInfo"></div>
|
||||
</div>
|
||||
|
||||
<div class="Form-row Form-row--centered has-label">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="Form-label">Viewer user</label>
|
||||
</div>
|
||||
<div class="Form-rowData Form-rowData--longer InviteUsers-userType">
|
||||
<div class="Toggler js-toggler InviteUsers-userTypeToggler <% if (!viewerEnabled) { %>is-disabled<% } %>"
|
||||
<% if (!viewerEnabled) { %>title="You can't send viewer invitations because you don't have viewer seats"<% } %>>
|
||||
<input type="checkbox" id="InviteUsers-userType" name="viewer" class="js-input" <% if (!viewerEnabled) { %>disabled="disabled"<% } %>/>
|
||||
<label for="InviteUsers-userType"></label>
|
||||
</div>
|
||||
<div class="Form-label">Viewer users can only read, not write, data and maps</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="InviteUsers-formFooterSticky">
|
||||
<div class="Dialog-footer InviteUsers-formFooter">
|
||||
<div><%/* placeholder for flex layout */%></div>
|
||||
<button type="submit" class="CDB-Button CDB-Button--primary is-disabled js-submit">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Invite users</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const loadingView = require('builder/components/loading/render-loading');
|
||||
const template = require('./delete-notification-dialog.tpl');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'modalModel',
|
||||
'authenticityToken',
|
||||
'notificationId'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-submit': '_onSubmit',
|
||||
'click .js-cancel': '_closeDialog'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
const { authenticityToken, notificationId } = this.options;
|
||||
|
||||
return this.$el.html(template({
|
||||
formAction: `${this._userModel.get('base_url')}/organization/notifications/${notificationId}`,
|
||||
authenticityToken
|
||||
}));
|
||||
},
|
||||
|
||||
_onSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.$('form').submit();
|
||||
|
||||
this.$('.Modal-inner').hide();
|
||||
|
||||
this.$('.Modal-inner').after(loadingView({
|
||||
title: 'Removing...'
|
||||
}));
|
||||
},
|
||||
|
||||
_closeDialog: function (event) {
|
||||
event && event.preventDefault();
|
||||
this._modalModel.destroy();
|
||||
}
|
||||
});
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<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>
|
||||
<form accept-charset="UTF-8" action="<%- formAction %>" method="post">
|
||||
<input name="utf8" type="hidden" value="✓" />
|
||||
<input name="authenticity_token" type="hidden" value="<%- authenticityToken %>" />
|
||||
<input name="_method" type="hidden" value="delete" />
|
||||
|
||||
<h2 class=" CDB-Text CDB-Size-huge is-light u-bSpace--xl">You are about to remove a notification</h2>
|
||||
<p class="CDB-Text CDB-Size-large u-altTextColor">Are you sure you want to remove it?</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">Cancel</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="Modal-listActionsitem">
|
||||
<button class="CDB-Button CDB-Button--primary CDB-Button--big js-submit">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-medium u-upperCase">Ok, remove it</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const markdown = require('markdown');
|
||||
const $ = require('jquery');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const RemoveNotificationDialogView = require('./delete-notification-dialog-view');
|
||||
const SendButtonView = require('./send-button-view');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'userModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-resend': '_onClickResend',
|
||||
'click .js-remove': '_onClickRemove',
|
||||
'keydown .js-textarea': '_onTextareaKeydown',
|
||||
'input .js-textarea': '_updateCounter',
|
||||
'propertychange .js-textarea': '_updateCounter'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$textarea = this.$('#carto_notification_body');
|
||||
this._modals = new ModalsServiceModel();
|
||||
|
||||
this._initViews();
|
||||
this._initBinds();
|
||||
|
||||
this._updateCounter();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this.sendButton = new SendButtonView({
|
||||
needsPasswordConfirmation: this._userModel.get('needs_password_confirmation'),
|
||||
modals: this._modals
|
||||
});
|
||||
this.$('.js-send').html(this.sendButton.render().el);
|
||||
this.addView(this.sendButton);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.sendButton, 'submitForm', this._submitForm);
|
||||
},
|
||||
|
||||
_submitForm: function () {
|
||||
if (navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) {
|
||||
this.$('form').clone().appendTo('body').submit(); // FF only
|
||||
} else {
|
||||
this.$('form').submit();
|
||||
}
|
||||
},
|
||||
|
||||
_updateCounter: function () {
|
||||
const textLength = $(markdown.toHTML(this.$textarea.val())).text().length;
|
||||
this.sendButton.updateCounter(textLength);
|
||||
},
|
||||
|
||||
_onClickResend: function (event) {
|
||||
const $notificationItem = $(event.target).closest('.js-NotificationsList-item');
|
||||
const title = $notificationItem.find('.js-html_body').data('body');
|
||||
const recipients = $notificationItem.find('.js-recipients').data('recipients');
|
||||
|
||||
this.$textarea.val(title);
|
||||
this.$('input[name="carto_notification[recipients]"]').prop('checked', false);
|
||||
this.$('input[name="carto_notification[recipients]"][value="' + recipients + '"]').prop('checked', true);
|
||||
|
||||
$('body').animate({
|
||||
scrollTop: 0
|
||||
});
|
||||
|
||||
this._updateCounter();
|
||||
},
|
||||
|
||||
_onTextareaKeydown: function (event) {
|
||||
if (event.key === 'Enter' && event.metaKey) {
|
||||
this.sendButton.onUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
_onClickRemove: function (event) {
|
||||
const $target = $(event.target);
|
||||
|
||||
this._modals.create((modalModel) => (
|
||||
new RemoveNotificationDialogView({
|
||||
userModel: this._userModel,
|
||||
modalModel,
|
||||
authenticityToken: this.options.authenticityToken,
|
||||
notificationId: $target.data('id')
|
||||
})
|
||||
));
|
||||
}
|
||||
});
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const Backbone = require('backbone');
|
||||
const PasswordValidatedForm = require('dashboard/helpers/password-validated-form');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const template = require('./send-button.tpl');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'modals',
|
||||
'needsPasswordConfirmation'
|
||||
];
|
||||
|
||||
const MAX_NOTIFICATION_LENGTH = 140;
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'FormAccount-rowData',
|
||||
|
||||
events: {
|
||||
'click .js-button': 'onUpdate'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this.model = new Backbone.Model({
|
||||
status: 'idle',
|
||||
counter: 0
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(template({
|
||||
isLoading: this._isLoading(),
|
||||
isDisabled: this._isDisabled(),
|
||||
isNegative: this._isNegative(),
|
||||
counter: MAX_NOTIFICATION_LENGTH - this.model.get('counter')
|
||||
}));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
updateCounter: function (strLen) {
|
||||
this.model.set('counter', strLen);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.model.bind('change', this.render, this);
|
||||
},
|
||||
|
||||
_isNegative: function () {
|
||||
return this.model.get('counter') > MAX_NOTIFICATION_LENGTH;
|
||||
},
|
||||
|
||||
_isDisabled: function () {
|
||||
return (this.model.get('counter') === 0) || this._isNegative() || this._isLoading();
|
||||
},
|
||||
|
||||
_isLoading: function () {
|
||||
return this.model.get('status') === 'loading';
|
||||
},
|
||||
|
||||
_submit: function () {
|
||||
this.trigger('submitForm');
|
||||
},
|
||||
|
||||
onUpdate: function (event) {
|
||||
this.killEvent(event);
|
||||
|
||||
const form = $('#new_carto_notification');
|
||||
|
||||
if (this._isDisabled()) return false;
|
||||
|
||||
if (!this._needsPasswordConfirmation) {
|
||||
this.model.set({ status: 'loading' });
|
||||
return this._submit();
|
||||
}
|
||||
|
||||
PasswordValidatedForm.showPasswordModal({
|
||||
modalService: this._modals,
|
||||
onPasswordTyped: password => {
|
||||
PasswordValidatedForm.addPasswordToForm(form, password, {
|
||||
doNotSubmit: true
|
||||
});
|
||||
|
||||
this.model.set({ status: 'loading' });
|
||||
this._submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<p class="Md-counter CDB-Text CDB-Size-medium u-secondaryTextColor u-rSpace--xl <% if (isNegative) { %>Md-counter--negative<% } %>"><%= counter %></p>
|
||||
|
||||
<button class="OrganizationNotifications-button CDB-Button CDB-Button--primary <% if (isDisabled) { %>is-disabled<% } %> js-button js-save">
|
||||
<div class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase u-flex">
|
||||
<% if (isLoading) { %>
|
||||
<div class="CDB-LoaderIcon CDB-LoaderIcon--small u-iBlock">
|
||||
<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>
|
||||
Sending
|
||||
<% } else { %>
|
||||
Send
|
||||
<% } %>
|
||||
</div>
|
||||
</button>
|
||||
@@ -0,0 +1,14 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
|
||||
/* Organization user form */
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'submit': '_validateFormSubmit'
|
||||
},
|
||||
|
||||
_validateFormSubmit: function (event) {
|
||||
if (this.$('.js-userQuotaError').length > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
|
||||
/* Progress quota bar for organization users input */
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'keyup .js-assignedSize': '_onKeyup'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
this._initViews();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:quota_in_bytes', this._onQuotaChange);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
const quotaInMb = Math.max(Math.floor(this.model.get('quota_in_bytes') / 1024 / 1024).toFixed(0), 1);
|
||||
this.$('.js-assignedSize').val(quotaInMb);
|
||||
},
|
||||
|
||||
_onQuotaChange: function () {
|
||||
this.$('.js-assignedSize').val(Math.max(this.model.assignedQuotaInRoundedMb(), 1));
|
||||
},
|
||||
|
||||
_onKeyup: function () {
|
||||
const modifiedQuotaInBytes = Math.max(Math.floor(this.$('.js-assignedSize').val() * 1048576), 1048576);
|
||||
const assignedPer = (modifiedQuotaInBytes * 100) / this.model.organization.get('available_quota_for_user');
|
||||
const errorMessage = '<p class="FormAccount-rowInfoText FormAccount-rowInfoText--error js-userQuotaError">Invalid quota, insert a valid one.</p>';
|
||||
|
||||
if (!isNaN(assignedPer)) {
|
||||
this.model.set('quota_in_bytes', modifiedQuotaInBytes);
|
||||
}
|
||||
|
||||
if (isNaN(assignedPer) || (this.model.get('quota_in_bytes') > this.model.organization.get('available_quota_for_user')) || (this.model.get('quota_in_bytes') < this.model.get('db_size_in_bytes'))) {
|
||||
this.$('.js-assignedSize').addClass('has-error');
|
||||
|
||||
if (this.$('.js-userQuotaError').length === 0) {
|
||||
$('.js-userQuotaSliderInput').append(errorMessage);
|
||||
}
|
||||
} else {
|
||||
this.$('.js-assignedSize').removeClass('has-error');
|
||||
this.$('.js-userQuotaError').remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
|
||||
const MAX_QUOTA_BITS = 104857600; // 100Mb
|
||||
|
||||
/* Progress quota bar for organization users */
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'hover .js-quotaProgressSlider': '_showTooltip'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
this._initViews();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:quota_in_bytes', this._onQuotaChange);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
const userMinQuota = MAX_QUOTA_BITS / this.model.organization.get('available_quota_for_user');
|
||||
|
||||
// Init slider
|
||||
this.$('.js-slider').slider({
|
||||
max: 100,
|
||||
min: userMinQuota,
|
||||
step: 1,
|
||||
value: this.model.assignedQuotaPercentage(),
|
||||
range: 'min',
|
||||
orientation: 'horizontal',
|
||||
slide: this._onSlideChange.bind(this)
|
||||
});
|
||||
|
||||
$('.ui-slider-handle').addClass('js-quotaProgressSlider');
|
||||
},
|
||||
|
||||
_onSlideChange: function (ev, ui) {
|
||||
const userQuota = Math.max(Math.floor((this.model.organization.get('available_quota_for_user') * ui.value) / 100), 1);
|
||||
const assignedPer = (userQuota * 100) / this.model.organization.get('available_quota_for_user');
|
||||
|
||||
if (ui.value >= this.model.usedQuotaPercentage()) {
|
||||
this.$('.ui-slider-range').css('width', `${assignedPer}%`);
|
||||
this.$('.js-quotaProgressSlider').css('left', `${assignedPer}%`);
|
||||
|
||||
const tooltipLeft = $('.js-quotaProgressSlider').offset().left -
|
||||
($('.js-orgUserQuotaTooltip').outerWidth() / 2) +
|
||||
($('.js-quotaProgressSlider').outerWidth() / 2);
|
||||
|
||||
$('.js-orgUserQuotaTooltip').css('left', tooltipLeft);
|
||||
|
||||
this.model.set('quota_in_bytes', userQuota);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_onQuotaChange: function () {
|
||||
if (this.model.assignedQuotaPercentage() >= this.model.usedQuotaPercentage()) {
|
||||
const assignedQuotaPercentage = Math.min(this.model.assignedQuotaPercentage(), 100);
|
||||
|
||||
$('.ui-slider-range').css('width', `${assignedQuotaPercentage}%`);
|
||||
$('.js-quotaProgressSlider').css('left', `${assignedQuotaPercentage}%`);
|
||||
}
|
||||
},
|
||||
|
||||
_showTooltip: function () {
|
||||
const viewModel = this.model;
|
||||
|
||||
this.addView(
|
||||
new TipsyTooltipView({
|
||||
el: $('.js-quotaProgressSlider'),
|
||||
className: 'js-orgUserQuotaTooltip',
|
||||
title: function () {
|
||||
const quotaPercentage = viewModel.assignedQuotaPercentage().toFixed(0);
|
||||
|
||||
return `${quotaPercentage}% of the available org quota`;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const UserQuotaSlider = require('./organization-user-quota-slider');
|
||||
const UserQuotaSliderInput = require('./organization-user-quota-slider-input');
|
||||
|
||||
/* Progress quota bar for organization users */
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function () {
|
||||
this._initBinds();
|
||||
this._initViews();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:quota_in_bytes', this._onQuotaChange);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
// Quota slider
|
||||
const userQuotaSlider = new UserQuotaSlider({
|
||||
el: this.$('.js-userQuotaSlider'),
|
||||
model: this.model
|
||||
});
|
||||
|
||||
this.addView(userQuotaSlider);
|
||||
|
||||
// Quota slider input
|
||||
const userQuotaSliderInput = new UserQuotaSliderInput({
|
||||
el: this.$('.js-userQuotaSliderInput'),
|
||||
model: this.model
|
||||
});
|
||||
|
||||
this.addView(userQuotaSliderInput);
|
||||
},
|
||||
|
||||
_onQuotaChange: function (model, quota) {
|
||||
this.$('#user_quota').val(quota);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
<ul class="DropdownList DropdownList--list CDB-Text CDB-Size-medium">
|
||||
<li class="DropdownList-item DropdownList-item--verticalPadding"><a href="<%- createUrl %>">Create User</a></li>
|
||||
<li class="DropdownList-item DropdownList-item--verticalPadding"><a href="#" class="js-inviteUser">Invite User</a></li>
|
||||
</ul>
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const Utils = require('builder/helpers/utils');
|
||||
const pluralize = require('dashboard/helpers/pluralize');
|
||||
const template = require('./organization-user.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'OrganizationList-user',
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
totalPer: this.options.totalPer,
|
||||
userPer: this.options.userPer,
|
||||
usedPer: this.options.usedPer,
|
||||
isOwner: this.options.isOwner,
|
||||
isAdmin: this.options.isAdmin,
|
||||
isViewer: this.options.isViewer,
|
||||
editable: this.options.editable,
|
||||
url: this.options.url,
|
||||
sizeOnBytes: Utils.readablizeBytes(this.model.get('db_size_in_bytes')),
|
||||
quotaInBytes: Utils.readablizeBytes(this.model.get('quota_in_bytes')),
|
||||
avatarUrl: this.model.get('avatar_url'),
|
||||
username: this.model.get('username'),
|
||||
user_email: this.model.get('email'),
|
||||
table_count: pluralize.prefixWithCount('Dataset', 'Datasets', this.model.get('table_count')),
|
||||
maps_count: pluralize.prefixWithCount('Map', 'Maps', this.model.get('all_visualization_count'))
|
||||
})
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<% if (editable) { %>
|
||||
<a class="OrganizationList-userLink" href="<%- url %>">
|
||||
<% } else { %>
|
||||
<span class="OrganizationList-userLink is-disabled">
|
||||
<% } %>
|
||||
<div class="OrganizationList-userAvatar UserAvatar">
|
||||
<img src="<%- avatarUrl %>" alt="<%- username %>" src="<%- username %>" class="UserAvatar-img UserAvatar-img--medium-large OrganizationList-userAvatar--img" />
|
||||
</div>
|
||||
<div class="OrganizationList-userInfo">
|
||||
<div class="OrganizationList-userInfoName">
|
||||
<div class="u-flex u-alignCenter">
|
||||
<h3 class="CDB-Text u-ellipsLongText CDB-Size-medium is-semibold u-rSpace" title="<%- username %>"><%- username %></h3>
|
||||
<% if (isOwner) { %>
|
||||
<span class="UserRoleIndicator UserRoleIndicator--filled is-green u-lSpace">OWNER</span>
|
||||
<% } else if (isAdmin) { %>
|
||||
<span class="UserRoleIndicator UserRoleIndicator--filled is-grey u-lSpace">ADMIN</span>
|
||||
<% } %>
|
||||
<span class="UserRoleIndicator u-altTextColor u-lSpace">
|
||||
<% if (isViewer) { %>
|
||||
VIEWER
|
||||
<% } else { %>
|
||||
BUILDER
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
<h4 class="OrganizationList-userInfoSubtitle u-ellipsLongText" title="<%- user_email %>"><%- user_email %></h4>
|
||||
</div>
|
||||
<ul class="OrganizationList-userInfoData u-upperCase u-altTextColor">
|
||||
<li class="CDB-Text CDB-Size-small u-rSpace--xl u-flex u-alignCenter">
|
||||
<i class="CDB-IconFont CDB-IconFont-map CDB-Size-medium u-rSpace"></i>
|
||||
<span><%- maps_count %></span>
|
||||
</li>
|
||||
<li class="CDB-Text CDB-Size-small u-flex u-alignCenter">
|
||||
<i class="CDB-IconFont CDB-IconFont-rows CDB-Size-medium u-rSpace"></i>
|
||||
<span><%- table_count %></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% if (editable) { %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
</span>
|
||||
<% } %>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const template = require('./organization-users-footer.tpl');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Form-footer',
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
seats: this.model.get('seats'),
|
||||
users: this.options.organizationUsers.totalCount(),
|
||||
newUserUrl: this.model.viewUrl().create(),
|
||||
upgradeUrl: window.upgrade_url,
|
||||
customHosted: this._configModel.isHosted()
|
||||
})
|
||||
);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:total_users', this.render);
|
||||
this.listenTo(this.options.organizationUsers, 'sync', this.render);
|
||||
}
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<% if (seats > users) { %>
|
||||
<% if ((seats - users) < 5) { %>
|
||||
<p class="CDB-Text Form-footerText">
|
||||
<i class="CDB-IconFont CDB-IconFont-info Form-footerIcon"></i>
|
||||
You are near your seats limit. Consider to <a href="<%- upgradeUrl %>">upgrade your account</a>.
|
||||
</p>
|
||||
<% } else { %>
|
||||
<em></em>
|
||||
<% } %>
|
||||
<% } else if (!customHosted) { %>
|
||||
<p class="CDB-Text Form-footerText">
|
||||
<i class="CDB-IconFont CDB-IconFont-info Form-footerIcon"></i>
|
||||
<a href="mailto:sales@carto.com">Contact us</a> if you have any questions.
|
||||
</p>
|
||||
<a href="<%- upgradeUrl %>" class="CDB-Text Button Button--positive"><span>Upgrade account</span></a>
|
||||
<% } %>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const PaginationView = require('builder/components/pagination/pagination-view.js');
|
||||
const OrganizationUserView = require('./organization-user-view');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'organization',
|
||||
'paginationModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.empty();
|
||||
|
||||
// Users list
|
||||
const $ul = $('<ul>').addClass('OrganizationList');
|
||||
this.$el.append($ul);
|
||||
|
||||
let totalPer = 0;
|
||||
this.collection.each(function (user) {
|
||||
// Calculations to create organization user bars
|
||||
const userPer = (user.get('quota_in_bytes') * 100) / this._organization.get('quota_in_bytes');
|
||||
const usedPer = (user.get('db_size_in_bytes') * 100) / this._organization.get('quota_in_bytes');
|
||||
user.organization = this._organization;
|
||||
|
||||
const view = new OrganizationUserView({
|
||||
model: user,
|
||||
isOwner: user.isOrgOwner(),
|
||||
isAdmin: user.isOrgAdmin(),
|
||||
isViewer: user.get('viewer'),
|
||||
editable: this._userModel.isOrgOwner() || this._userModel.id === user.id || !user.isOrgAdmin(),
|
||||
userPer: userPer,
|
||||
usedPer: usedPer,
|
||||
totalPer: totalPer,
|
||||
url: this._organization.viewUrl().edit(user)
|
||||
});
|
||||
|
||||
$ul.append(view.render().el);
|
||||
this.addView(view);
|
||||
|
||||
totalPer = totalPer + userPer;
|
||||
}, this);
|
||||
|
||||
// Paginator
|
||||
const $paginatorWrapper = $('<div>').addClass('OrganizationList-paginator');
|
||||
this.$el.append($paginatorWrapper);
|
||||
const paginationView = new PaginationView({
|
||||
model: this._paginationModel
|
||||
});
|
||||
$paginatorWrapper.append(paginationView.render().el);
|
||||
this.addView(paginationView);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.collection, 'sync', this.render);
|
||||
}
|
||||
});
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const Utils = require('builder/helpers/utils');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const PaginationModel = require('builder/components/pagination/pagination-model');
|
||||
const template = require('./organization-users.tpl');
|
||||
const OrganizationUsersFooterView = require('./organization-users-footer-view');
|
||||
const DropdownAdminView = require('dashboard/components/dropdown/dropdown-admin-view');
|
||||
const PagedSearchModel = require('dashboard/data/paged-search-model');
|
||||
const TabPane = require('dashboard/components/tabpane/tabpane');
|
||||
const InviteUsersDialogView = require('../invite-users/invite-users-dialog-view');
|
||||
const addUsersTemplate = require('./add-users.tpl');
|
||||
const OrganizationUsersListView = require('./organization-users-list-view');
|
||||
const loadingView = require('builder/components/loading/render-loading');
|
||||
const noResultsTemplate = require('builder/components/no-results/no-results.tpl');
|
||||
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'configModel',
|
||||
'organization',
|
||||
'organizationUsers'
|
||||
];
|
||||
|
||||
/**
|
||||
* Organization users content, list, pagination,
|
||||
* form, footer...
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-addUserOptions': '_openAddUserDropdown',
|
||||
'click .js-search-link': '_onSearchClick',
|
||||
'click .js-clean-search': '_onCleanSearchClick',
|
||||
'keydown .js-search-input': '_onKeyDown',
|
||||
'submit .js-search-form': 'killEvent'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._modals = new ModalsServiceModel();
|
||||
|
||||
this.pagedSearchModel = new PagedSearchModel({
|
||||
per_page: 50,
|
||||
order: 'username'
|
||||
});
|
||||
|
||||
this.paginationModel = new PaginationModel({
|
||||
current_page: this.pagedSearchModel.get('page'),
|
||||
total_count: this._organizationUsers.totalCount(),
|
||||
per_page: this.pagedSearchModel.get('per_page')
|
||||
});
|
||||
|
||||
// Include current user in fetch results
|
||||
this._organizationUsers.excludeCurrentUser(false);
|
||||
|
||||
this._initBinds();
|
||||
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
seats: this._organization.get('seats'),
|
||||
assigned_seats: this._organization.get('assigned_seats'),
|
||||
viewer_seats: this._organization.get('viewer_seats'),
|
||||
assigned_viewer_seats: this._organization.get('assigned_viewer_seats'),
|
||||
newUserUrl: this._organization.viewUrl().create()
|
||||
})
|
||||
);
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._organizationUsers, 'fetching', function () {
|
||||
this._panes && this._panes.active('loading');
|
||||
});
|
||||
|
||||
this.listenTo(this._organizationUsers, 'error', function (error) {
|
||||
// Old requests can be stopped, so aborted requests are not
|
||||
// considered as an error
|
||||
if (!error || (error && error.statusText !== 'abort')) {
|
||||
this._panes.active('error');
|
||||
}
|
||||
});
|
||||
|
||||
this.listenTo(this._organizationUsers, 'sync', function (collection) {
|
||||
const total = collection.totalCount();
|
||||
|
||||
this.paginationModel.set({
|
||||
total_count: total,
|
||||
current_page: this.pagedSearchModel.get('page')
|
||||
});
|
||||
this._panes && this._panes.active(total > 0 ? 'users' : 'no_results');
|
||||
});
|
||||
|
||||
this.listenTo(this._organizationUsers, 'sync error loading', function (collection) {
|
||||
this[this.pagedSearchModel.get('q') ? '_showCleanSearchButton' : '_hideCleanSearchButton']();
|
||||
});
|
||||
|
||||
this.listenTo(this.paginationModel, 'change:current_page', function (model) {
|
||||
const newPage = model.get('current_page');
|
||||
|
||||
this.pagedSearchModel.set('page', newPage);
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
});
|
||||
|
||||
$(document).on('click', '.js-inviteUser', this._onInviteClick.bind(this));
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._panes = new TabPane({
|
||||
el: this.$('.js-organizationUsersPanes')
|
||||
});
|
||||
this.addView(this._panes);
|
||||
|
||||
this._panes.addTab('users',
|
||||
new OrganizationUsersListView({
|
||||
organization: this._organization,
|
||||
collection: this._organizationUsers,
|
||||
paginationModel: this.paginationModel,
|
||||
userModel: this._userModel
|
||||
}).render()
|
||||
);
|
||||
|
||||
this._panes.addTab('error', ViewFactory.createByHTML(errorTemplate({
|
||||
msg: ''
|
||||
})).render());
|
||||
|
||||
this._panes.addTab('no_results', ViewFactory.createByHTML(noResultsTemplate({
|
||||
icon: 'CDB-IconFont-defaultUser',
|
||||
title: 'Oh! No results',
|
||||
desc: 'Unfortunately we haven\'t found any user with these parameters'
|
||||
})).render());
|
||||
|
||||
this._panes.addTab('loading', ViewFactory.createByHTML(loadingView({
|
||||
title: 'Getting users...'
|
||||
})).render());
|
||||
|
||||
// Form footer
|
||||
const footer = new OrganizationUsersFooterView({
|
||||
model: this._organization,
|
||||
organizationUsers: this._organizationUsers,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this.addView(footer);
|
||||
this.$el.append(footer.render().el);
|
||||
|
||||
let activePane = 'loading';
|
||||
|
||||
if (this._organizationUsers.totalCount() === 0) {
|
||||
activePane = 'no_results';
|
||||
} else if (this._organizationUsers.totalCount() > 0) {
|
||||
activePane = 'users';
|
||||
}
|
||||
|
||||
this._panes.active(activePane);
|
||||
},
|
||||
|
||||
_openAddUserDropdown: function (event) {
|
||||
if (this._dropdownView) this._dropdownView.clean();
|
||||
|
||||
this._dropdownView = new DropdownAdminView({
|
||||
className: 'Dropdown border',
|
||||
target: $(event.target),
|
||||
width: 120,
|
||||
template: addUsersTemplate,
|
||||
vertical_position: 'down',
|
||||
horizontal_position: 'right',
|
||||
createUrl: this._organization.viewUrl().create(),
|
||||
tick: 'right'
|
||||
});
|
||||
|
||||
$('body').append(this._dropdownView.render().el);
|
||||
|
||||
this._dropdownView.on('onDropdownHidden', function () {
|
||||
this._dropdownView.clean();
|
||||
}, this);
|
||||
|
||||
this._dropdownView.open(event);
|
||||
},
|
||||
|
||||
_showCleanSearchButton: function () {
|
||||
this.$('.js-clean-search').show();
|
||||
},
|
||||
|
||||
_hideCleanSearchButton: function () {
|
||||
this.$('.js-clean-search').hide();
|
||||
},
|
||||
|
||||
_focusSearchInput: function () {
|
||||
var $searchInput = this._$searchInput();
|
||||
$searchInput.focus().val($searchInput.val());
|
||||
},
|
||||
|
||||
_onSearchClick: function (e) {
|
||||
if (e) this.killEvent(e);
|
||||
this._$searchInput().focus();
|
||||
},
|
||||
|
||||
_onCleanSearchClick: function (ev) {
|
||||
this.killEvent(ev);
|
||||
this._cleanSearch();
|
||||
},
|
||||
|
||||
_onKeyDown: function (ev) {
|
||||
var enterPressed = (ev.keyCode === $.ui.keyCode.ENTER);
|
||||
var escapePressed = (ev.keyCode === $.ui.keyCode.ESCAPE);
|
||||
if (enterPressed) {
|
||||
this.killEvent(ev);
|
||||
this._submitSearch();
|
||||
} else if (escapePressed) {
|
||||
this.killEvent(ev);
|
||||
if (this.pagedSearchModel.get('q')) {
|
||||
this._cleanSearch();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_submitSearch: function (e) {
|
||||
var search = this._$searchInput().val().trim();
|
||||
this.pagedSearchModel.set({
|
||||
q: Utils.stripHTML(search),
|
||||
page: 1
|
||||
});
|
||||
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
},
|
||||
|
||||
_$searchInput: function () {
|
||||
return this.$('.js-search-input');
|
||||
},
|
||||
|
||||
_cleanSearch: function () {
|
||||
this._$searchInput().val('');
|
||||
this.pagedSearchModel.set({
|
||||
q: '',
|
||||
page: 1
|
||||
});
|
||||
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
},
|
||||
|
||||
_onInviteClick: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this._dropdownView.clean();
|
||||
|
||||
this._modals.create(modalModel =>
|
||||
new InviteUsersDialogView({
|
||||
configModel: this._configModel,
|
||||
organization: this._organization,
|
||||
organizationUsers: this._organizationUsers,
|
||||
modalModel
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._organizationUsers.restoreExcludeCurrentUser();
|
||||
$(document).off('click', '.js-inviteUser', this._onInviteClick.bind(this));
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
});
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<div class="OrganizationSearch">
|
||||
<div class="OrganizationSearch-Item">
|
||||
<button class="OrganizationSearch-FormButton js-search-link u-alignCenter CDB-Text CDB-Size-medium u-actionTextColor">
|
||||
<i class="CDB-IconFont CDB-IconFont-lens"></i>Search
|
||||
</button>
|
||||
</div>
|
||||
<div class="OrganizationSearch-Item">
|
||||
<form class="OrganizationSearch-Form js-search-form" action="#">
|
||||
<input class="OrganizationSearch-FormInput CDB-Text CDB-Size-medium u-secondaryTextColor js-search-input" type="text" placeholder="Search by username or email" />
|
||||
<button type="button" class="CDB-Text CDB-Size-large u-actionTextColor OrganizationSearch-FormButton--clean js-clean-search" style="display: none;">x</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="u-flex u-alignCenter CDB-Text CDB-Size-small u-secondaryTextColor">
|
||||
Builder (<%- assigned_seats %>/<%- seats %>) - Viewer (<%- assigned_viewer_seats %>/<%- viewer_seats %>)
|
||||
</div>
|
||||
<div class="OrganizationSearch-Item">
|
||||
<button class="CDB-Button CDB-Button--secondary CDB-Button--small js-addUserOptions">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">
|
||||
Add users
|
||||
<i class="Button-arrowMenu CDB-IconFont CDB-IconFont-caretDown"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="CDB-Text js-organizationUsersPanes"></div>
|
||||
@@ -0,0 +1,43 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const template = require('./regenerate-keys.tpl');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'modalModel',
|
||||
'type',
|
||||
'scope',
|
||||
'form_action',
|
||||
'passwordNeeded',
|
||||
'authenticity_token'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
events: {
|
||||
'click .js-cancel': '_closeDialog'
|
||||
},
|
||||
|
||||
defaults: {
|
||||
method: 'post'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._method = this.options.method;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return this.$el.html(template({
|
||||
type: this._type,
|
||||
scope: this._scope,
|
||||
form_action: this._form_action,
|
||||
passwordNeeded: this._passwordNeeded,
|
||||
authenticity_token: this._authenticity_token,
|
||||
method: this._method
|
||||
}));
|
||||
},
|
||||
|
||||
_closeDialog: function () {
|
||||
this._modalModel.destroy();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
<%
|
||||
if (scope === 'organization') {
|
||||
person_scope = 'You and all organization users';
|
||||
} else if (scope === 'organization_user') {
|
||||
person_scope = 'The user';
|
||||
} else {
|
||||
person_scope = 'You';
|
||||
}
|
||||
%>
|
||||
|
||||
<div class="CDB-Text Dialog-header u-inner">
|
||||
<div class="Dialog-headerIcon Dialog-headerIcon--negative">
|
||||
<i class="CDB-IconFont CDB-IconFont-keys"></i>
|
||||
</div>
|
||||
<p class="Dialog-headerTitle u-ellipsLongText">
|
||||
<% if (scope === 'organization') { %>
|
||||
You are about to regenerate the <%- type === "api" ? 'API keys' : 'OAuth credentials' %> for all the users of the organization
|
||||
<% } else if (scope === 'organization_user') { %>
|
||||
You are about to regenerate the <%- type === "api" ? 'API keys' : 'OAuth credentials' %> for this user of the organization
|
||||
<% } else { %>
|
||||
You are about to regenerate your <%- type === "api" ? 'API keys' : 'OAuth credentials' %>
|
||||
<% } %>
|
||||
</p>
|
||||
<p class="Dialog-headerText">
|
||||
<% if (type === "api") { %>
|
||||
<%- person_scope %> will need to update all deployed apps with the new API keys. Are you sure you want to continue?
|
||||
<% } else { %>
|
||||
<%- person_scope %> will have to update all OAuth keys in apps where you are using CARTO. Are you sure?
|
||||
<% } %>
|
||||
</p>
|
||||
</div>
|
||||
<form action="<%- form_action %>" method="post">
|
||||
<% if (passwordNeeded) { %>
|
||||
<div class="CDB-Text Dialog-body">
|
||||
<div class="Form-row Form-row--centered has-label">
|
||||
<div class="Form-rowLabel">
|
||||
<label class="Form-label">Your password</label>
|
||||
</div>
|
||||
<div class="Form-rowData">
|
||||
<input type="password" id="deletion_password_confirmation" name="password_confirmation" class="CDB-InputText CDB-Text Form-input Form-input--long" value=""/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="Dialog-footer u-inner">
|
||||
<button type="button" class="CDB-Button CDB-Button--secondary js-cancel">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">cancel</span>
|
||||
</button>
|
||||
|
||||
<input name="utf8" type="hidden" value="✓" />
|
||||
<input name="_method" type="hidden" value="<%- method %>" />
|
||||
<input name="authenticity_token" type="hidden" value="<%- authenticity_token %>" />
|
||||
<button type="submit" class="CDB-Button CDB-Button--error">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">Regenerate <%- type === "api" ? 'API keys' : 'OAuth credentials' %></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
Reference in New Issue
Block a user