Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions

View File

@@ -0,0 +1,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' });
});
}
});

View File

@@ -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>

View File

@@ -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();
}
});

View File

@@ -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>

View File

@@ -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();
}
});

View File

@@ -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>

View File

@@ -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 });
}
});

View File

@@ -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>

View File

@@ -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 });
}
});

View File

@@ -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>

View File

@@ -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);
}
});

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'
});
}
});

View File

@@ -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'));
}
});

View File

@@ -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>

View File

@@ -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);
}
});

View File

@@ -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();
}
}
}
});

View File

@@ -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;
}
});

View File

@@ -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 %> &bull; <%- 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>

View File

@@ -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)
});
});
}
});

View File

@@ -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;
}
});

View File

@@ -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: ''
}));
}
});