Commit 46f569b1 authored by Clement Ho's avatar Clement Ho

[skip ci] Refactor and code cleanup

parent 3a57d2a9
......@@ -856,6 +856,7 @@ GitLabDropdown = (function() {
let toggleText = this.options.toggleLabel(selected, el, instance);
if (this.options.updateLabel) {
// Option to override the dropdown label text
toggleText = this.options.updateLabel;
}
......
......@@ -52,6 +52,13 @@
$collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut();
var getSelected = function() {
return $selectbox
.find(`input[name="${$dropdown.data('field-name')}"]`)
.map((index, input) => parseInt(input.value, 10))
.get();
};
var updateIssueBoardsIssue = function () {
$loading.fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
......@@ -86,7 +93,6 @@
}
});
assignTo = function(selected) {
var data;
data = {};
data[abilityName] = {};
......@@ -100,11 +106,9 @@
url: issueURL,
data: data
}).done(function(data) {
var user;
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
// $selectbox.hide();
if (data.assignee) {
user = {
name: data.assignee.name,
......@@ -131,6 +135,8 @@
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
const glDropdown = this.instance || this.options.instance;
glDropdown.options.processData(term, users, callback);
}.bind(this));
......@@ -177,25 +183,21 @@
}
if ($dropdown.hasClass('js-multiselect')) {
const selected = $selectbox
.find('input[name="' + $dropdown.data('field-name') + '"]')
.map(function () {
return parseInt(this.value, 10);
})
.get()
.filter((i) => i !== 0);
const selected = getSelected().filter(i => i !== 0);
if (selected.length > 0) {
showDivider += 1;
users.splice(showDivider, 0, {
header: $dropdown.data('dropdown-header') || 'Assignee(s)',
});
if ($dropdown.data('dropdown-header')) {
showDivider += 1;
users.splice(showDivider, 0, {
header: $dropdown.data('dropdown-header') || 'Assignee(s)',
});
}
const selectedUsers = users
.filter((u) => selected.indexOf(u.id) !== -1)
.filter(u => selected.indexOf(u.id) !== -1)
.sort((a, b) => a.name > b.name);
users = users.filter((u) => selected.indexOf(u.id) === -1);
users = users.filter(u => selected.indexOf(u.id) === -1);
selectedUsers.forEach((selectedUser) => {
showDivider += 1;
......@@ -205,7 +207,6 @@
users.splice(showDivider + 1, 0, 'divider');
}
}
}
callback(users);
......@@ -224,12 +225,11 @@
const inputValue = glDropdown.filterInput.val();
if (this.multiSelect && inputValue === '') {
const users = glDropdown.fullData.filter((r) => {
return typeof r === 'object'
// Remove non-users from the fullData array
const users = glDropdown.fullData.filter(r => typeof r === 'object'
&& !Object.prototype.hasOwnProperty.call(r, 'beforeDivider')
&& !Object.prototype.hasOwnProperty.call(r, 'header');
});
&& !Object.prototype.hasOwnProperty.call(r, 'header')
);
const callback = glDropdown.parseData.bind(glDropdown);
// Update the data model
......@@ -247,17 +247,8 @@
}
},
defaultLabel: defaultLabel,
// inputId: 'issue_assignee_id',
hidden: function(e) {
if ($dropdown.hasClass('js-multiselect')) {
const selected = $selectbox
.find('input[name="' + $dropdown.data('field-name') + '"]')
.map(function() {
return parseInt(this.value, 10);
})
.get();
gl.sidebarAssigneesOptions.assignees.saveUsers();
}
......@@ -274,7 +265,6 @@
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(user, $el, e, isMarking, glDropdown) {
if ($dropdown.hasClass('js-multiselect')) {
const isActive = $el.hasClass('is-active');
const previouslySelected = $dropdown.closest('.selectbox')
.find("input[name='" + ($dropdown.data('field-name')) + "'][value!=0]");
......@@ -284,7 +274,7 @@
previouslySelected.each((index, element) => {
const id = parseInt(element.value, 10);
gl.sidebarAssigneesOptions.assignees.removeUser(id);
element.remove()
element.remove();
});
} else if (isActive) {
// user selected
......@@ -353,11 +343,7 @@
const $el = $(e.currentTarget);
$el.find('.is-active').removeClass('is-active');
const initialSelected = $selectbox
.find('input[name="' + $dropdown.data('field-name') + '"]')
.map(function () {
return this.value;
}).get().forEach((selectedId) => {
const initialSelected = getSelected().forEach((selectedId) => {
$el.find(`li[data-user-id="${selectedId}"] .dropdown-menu-user-link`).addClass('is-active');
});
},
......@@ -367,14 +353,12 @@
username = user.username ? "@" + user.username : "";
avatar = user.avatar_url ? user.avatar_url : false;
fieldName = this.fieldName;
field = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "'][value='" + user.id + "']");
// debugger
const fieldName = this.fieldName;
const field = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "'][value='" + user.id + "']");
if (field.length) {
selected = true;
}
// selected = user.id === parseInt(selectedId, 10) ? "is-active" : "";
img = "";
if (user.beforeDivider != null) {
`<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${user.name}</a></li>`;
......@@ -383,8 +367,8 @@
img = "<img src='" + avatar + "' class='avatar avatar-inline' width='30' />";
}
}
// split into three parts so we can remove the username section if nessesary
const listItem = `
return `
<li data-user-id=${user.id}>
<a href='#' class='dropdown-menu-user-link ${selected === true ? 'is-active' : ''}'>
${img}
......@@ -395,14 +379,6 @@
</a>
</li>
`;
// listWithUserName = "<span class='dropdown-menu-user-username'> " + username + " </span>";
// listClosingTags = "</a> </li>";
// if (username === '') {
// listWithUserName = '';
// }
// debugger
// return listWithName + listWithUserName + listClosingTags;
return listItem;
}
});
};
......
......@@ -3,12 +3,12 @@ export default {
props: {
loading: { type: Boolean, required: true },
numberOfAssignees: { type: Number, required: true },
editable: { type: Boolean, required: true},
editable: { type: Boolean, required: true },
},
computed: {
hasMultipleAssignees() {
return this.numberOfAssignees > 1;
}
},
},
template: `
<div class="title hide-collapsed">
......
export default {
name: 'CollapsedAvatar',
props: {
user: { type: Object, required: true },
},
computed: {
alt() {
return `${this.user.name}'s avatar`;
},
},
template: `
<button class="btn-link" type="button">
<img width="24"
class="avatar avatar-inline s24"
:alt="alt"
:src="user.avatarUrl" >
<span class="author">{{user.name}}</span>
</button>
`,
};
import CollapsedAvatar from './avatar';
export default {
name: 'CollapsedMultipleAssignees',
props: {
users: { type: Array, required: true }
users: { type: Array, required: true },
},
computed: {
title() {
......@@ -9,7 +11,7 @@ export default {
const firstFive = this.users.slice(0, max);
const names = [];
firstFive.forEach((u) => names.push(u.name));
firstFive.forEach(u => names.push(u.name));
if (this.users.length > max) {
names.push(`+${this.users.length - max} more`);
......@@ -18,21 +20,25 @@ export default {
return names.join(', ');
},
counter() {
let counter = `+${this.users.length - 1}`;
if (this.users.length > 99) {
return '99+';
} else {
return `+${this.users.length - 1}`;
counter = '99+';
}
return counter;
},
},
components: {
'collapsed-avatar': CollapsedAvatar,
},
template: `
<div class="sidebar-collapsed-icon sidebar-collapsed-user multiple-users"
data-container="body" data-placement="left"
data-toggle="tooltip" title="" :data-original-title="title">
<button class="btn-link" type="button">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[0].avatarUrl">
<span class="author">{{users[0].name}}</span>
</button>
data-container="body"
data-placement="left"
data-toggle="tooltip"
:data-original-title="title" >
<collapsed-avatar :user="users[0]" />
<button class="btn-link" type="button">
<span class="avatar-counter sidebar-avatar-counter">{{counter}}</span>
</button>
......
export default {
name: 'CollapsedNoAssignee',
props: {
users: { type: Array, required: true }
users: { type: Array, required: true },
},
template: `
<div class="sidebar-collapsed-icon sidebar-collapsed-user">
......
import CollapsedAvatar from './avatar';
export default {
name: 'CollapsedSingleAssignee',
props: {
users: { type: Array, required: true }
users: { type: Array, required: true },
},
components: {
'collapsed-avatar': CollapsedAvatar,
},
template: `
<div class="sidebar-collapsed-icon sidebar-collapsed-user"
data-container="body" data-placement="left"
data-toggle="tooltip" title="" :data-original-title="users[0].name">
<button class="btn-link" type="button">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[0].avatarUrl">
<span class="author">{{users[0].name}}</span>
</button>
data-container="body"
data-placement="left"
data-toggle="tooltip"
:data-original-title="users[0].name" >
<collapsed-avatar :user="users[0]" />
</div>
`,
};
import CollapsedAvatar from './avatar';
export default {
name: 'CollapsedTwoAssignees',
props: {
users: { type: Array, required: true }
users: { type: Array, required: true },
},
computed: {
title() {
return `${this.users[0].name}, ${this.users[1].name}`;
}
},
},
components: {
'collapsed-avatar': CollapsedAvatar,
},
template: `
<div class="sidebar-collapsed-icon sidebar-collapsed-user multiple-users"
data-container="body" data-placement="left"
data-toggle="tooltip" title="" :data-original-title="title">
<button class="btn-link" type="button">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[0].avatarUrl">
<span class="author">{{users[0].name}}</span>
</button>
<button class="btn-link" type="button">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[1].avatarUrl">
<span class="author">{{users[1].name}}</span>
</button>
data-container="body"
data-placement="left"
data-toggle="tooltip"
:data-original-title="title" >
<collapsed-avatar :user="users[0]" />
<collapsed-avatar :user="users[1]" />
</div>
`,
};
......@@ -4,11 +4,11 @@ export default {
name: 'MultipleAssignees',
data() {
return {
showMore: false
}
showMore: false,
};
},
props: {
assignees: { type: Object, required: true }
assignees: { type: Object, required: true },
},
computed: {
shouldShowMoreAssignees() {
......@@ -18,7 +18,7 @@ export default {
return this.showMore ? 0 : this.assignees.users.length - 5;
},
toggleShowMore() {
return function() {
return function toggleShowMore() {
this.showMore = !this.showMore;
}.bind(this);
},
......@@ -30,13 +30,22 @@ export default {
<div class="hide-collapsed">
<div class="hide-collapsed">
<div class="user-list">
<div class="user-item" v-for="(user, index) in assignees.users" v-if="showMore || (index < 5 && !showMore)">
<a class="user-link has-tooltip" data-placement="bottom" title="" :href="'/' + user.username" :data-title="user.name">
<img width="32" class="avatar avatar-inline s32 " alt="" :src="user.avatarUrl">
<div class="user-item" v-for="(user, index) in assignees.users"
v-if="showMore || (index < 5 && !showMore)" >
<a class="user-link has-tooltip"
data-placement="bottom"
:href="'/' + user.username"
:data-title="user.name" >
<img width="32"
class="avatar avatar-inline s32"
alt="PLACEHOLDER"
:src="user.avatarUrl" >
</a>
</div>
</div>
<show-more-assignees v-if="shouldShowMoreAssignees" :hiddenAssignees="numberOfHiddenAssignees" :toggle="toggleShowMore" />
<show-more-assignees v-if="shouldShowMoreAssignees"
:hiddenAssignees="numberOfHiddenAssignees"
:toggle="toggleShowMore" />
</div>
</div>
`,
......
......@@ -6,7 +6,7 @@ export default {
methods: {
assignSelf() {
this.assignees.addCurrentUser();
}
},
},
template: `
<div class="hide-collapsed">
......
......@@ -2,12 +2,12 @@ export default {
name: 'ShowMoreAssignees',
props: {
hiddenAssignees: { type: Number, required: true },
toggle: { type: Function, required: true }
toggle: { type: Function, required: true },
},
computed: {
showMore() {
return this.hiddenAssignees > 0;
}
},
},
template: `
<div class="user-list-more">
......
......@@ -7,11 +7,17 @@ export default {
user() {
return this.assignees.users[0];
},
avatarAlt() {
return `${this.user.name}'s avatar`;
},
},
template: `
<div class="hide-collapsed">
<a class="author_link bold" :href="'/' + user.username">
<img width="32" class="avatar avatar-inline s32" alt="" :src="user.avatarUrl">
<img width="32"
class="avatar avatar-inline s32"
:alt="avatarAlt"
:src="user.avatarUrl" >
<span class="author">{{user.name}}</span>
<span class="username">@{{user.username}}</span>
</a>
......
......@@ -36,26 +36,28 @@ const sidebarAssigneesOptions = () => ({
return this.assignees.users.length;
},
componentName() {
if (this.numberOfAssignees === 0) {
return 'no-assignee';
} else if (this.numberOfAssignees === 1) {
return 'single-assignee';
} else {
return 'multiple-assignees';
switch (this.numberOfAssignees) {
case 0:
return 'no-assignee';
case 1:
return 'single-assignee';
default:
return 'multiple-assignees';
}
},
hideComponent() {
return !this.assignees.saved;
},
collapsedComponentName() {
if (this.numberOfAssignees === 0) {
return 'collapsed-no-assignee';
} else if (this.numberOfAssignees === 1) {
return 'collapsed-single-assignee';
} else if (this.numberOfAssignees === 2) {
return 'collapsed-two-assignees';
} else {
return 'collapsed-multiple-assignees';
switch (this.numberOfAssignees) {
case 0:
return 'collapsed-no-assignee';
case 1:
return 'collapsed-single-assignee';
case 2:
return 'collapsed-two-assignees';
default:
return 'collapsed-multiple-assignees';
}
},
},
......@@ -78,7 +80,7 @@ const sidebarAssigneesOptions = () => ({
`,
});
document.addEventListener('DOMContentLoaded', () => {
// Expose this to window so that we can add assignees from glDropdown
window.gl.sidebarAssigneesOptions = new Vue(sidebarAssigneesOptions());
});
......@@ -14,7 +14,7 @@ export default class SidebarAssigneesService {
update(userIds) {
return this.sidebarAssigneeResource.update({
[this.field]: userIds
[this.field]: userIds,
});
}
}
......@@ -8,7 +8,7 @@ export default class SidebarAssigneesStore {
this.editable = editable;
}
addUser(id, name, username, avatarUrl, saved) {
addUser(id, name = '', username = '', avatarUrl = '', saved = false) {
this.users.push({
id,
name,
......@@ -16,40 +16,44 @@ export default class SidebarAssigneesStore {
avatarUrl,
});
if (!saved) {
this.saved = false;
}
// !saved means that this user was added to UI but not service
this.saved = saved;
}
addCurrentUser() {
this.addUserIds(this.currentUserId);
this.addUser(this.currentUserId);
this.saveUsers();
}
removeUser(id) {
this.saved = false;
this.users = this.users.filter((u) => u.id !== id);
this.users = this.users.filter(u => u.id !== id);
}
saveUsers() {
const ids = this.users.map((u) => u.id) || 0;
const ids = this.users.map(u => u.id);
// If there are no ids, that means we have to unassign (which is id = 0)
const payload = ids.length > 0 ? ids : 0;
this.loading = true;
this.service.update(ids.length > 0 ? ids : 0)
this.service.update(payload)
.then((response) => {
const data = response.data;
const assignee = data.assignee;
this.users = [];
if (assignee) {
this.addUser(assignee.id, assignee.name, assignee.username, assignee.avatar_url, true);
}
this.saved = true;
this.loading = false;
}).catch((err) => {
console.log(err);
console.log('error');
this.loading = false;
});
const data = response.data;
const assignee = data.assignee;
this.users = [];
// TODO: Update this to match backend response
if (assignee) {
this.addUser(assignee.id, assignee.name, assignee.username, assignee.avatar_url, true);
}
this.saved = true;
this.loading = false;
}).catch((err) => {
this.loading = false;
// TODO: Add correct error handling
throw err;
});
}
}
\ No newline at end of file
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment