Commit 9b156ad3 authored by Clement Ho's avatar Clement Ho

[skip ci] add collapsed sidebar assignees

parent 29ffdb55
......@@ -145,7 +145,7 @@
Sidebar.prototype.openDropdown = function(blockOrName) {
var $block;
$block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
$block.find('.edit-link').trigger('click');
$block.find('.edit-link:not(.hidden)').trigger('click');
if (!this.isOpen()) {
this.setCollapseAfterUpdate($block);
return this.toggleSidebar('open');
......
......@@ -262,6 +262,10 @@
}
$selectbox.hide();
// Recalculate where .value is because vue might have changed it
$block = $selectbox.closest('.block');
$value = $block.find('.value');
// display:block overrides the hide-collapse rule
return $value.css('display', '');
},
......
......@@ -3,6 +3,7 @@ export default {
props: {
loading: { type: Boolean, required: true },
numberOfAssignees: { type: Number, required: true },
editable: { type: Boolean, required: true},
},
computed: {
hasMultipleAssignees() {
......@@ -18,7 +19,7 @@ export default {
Assignee
</template>
<i aria-hidden="true" class="fa fa-spinner fa-spin block-loading" :class="{ hidden: !loading }"></i>
<a class="edit-link pull-right" href="#">Edit</a>
<a class="edit-link pull-right" :class="{ hidden: !editable }" href="#">Edit</a>
</div>
`,
};
export default {
name: 'CollapsedMultipleAssignees',
props: {
users: { type: Array, required: true }
},
computed: {
title() {
const max = this.users.length > 5 ? 5 : this.users.length;
const firstFive = this.users.slice(0, max);
const names = [];
firstFive.forEach((u) => names.push(u.name));
if (this.users.length > max) {
names.push(`+${this.users.length - max} more`);
}
return names.join(', ');
},
counter() {
if (this.users.length > 99) {
return '99+';
} else {
return `+${this.users.length - 1}`;
}
},
},
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">
<a class="author_link" :href="'/' + users[0].username">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[0].avatarUrl">
<span class="author">{{users[0].name}}</span>
</a>
<a class="author_link" href='#'>
<span class="avatar-counter sidebar-avatar-counter">{{counter}}</span>
</a>
</div>
`,
};
export default {
name: 'CollapsedNoAssignee',
props: {
users: { type: Array, required: true }
},
template: `
<div class="sidebar-collapsed-icon sidebar-collapsed-user">
<i aria-hidden="true" class="fa fa-user"></i>
</div>
`,
};
export default {
name: 'CollapsedSingleAssignee',
props: {
users: { type: Array, required: true }
},
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">
<a class="author_link" :href="'/' + users[0].username">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[0].avatarUrl">
<span class="author">{{users[0].name}}</span>
</a>
</div>
`,
};
export default {
name: 'CollapsedTwoAssignees',
props: {
users: { type: Array, required: true }
},
computed: {
title() {
return `${this.users[0].name}, ${this.users[1].name}`;
}
},
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">
<a class="author_link" :href="'/' + users[0].username">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[0].avatarUrl">
<span class="author">{{users[0].name}}</span>
</a>
<a class="author_link" :href="'/' + users[1].username">
<img width="24" class="avatar avatar-inline s24 " alt="" :src="users[1].avatarUrl">
<span class="author">{{users[1].name}}</span>
</a>
</div>
`,
};
import Vue from 'vue';
import AssigneeTitle from './components/assignee_title';
import NoAssignee from './components/no_assignee';
import SingleAssignee from './components/single_assignee';
import MultipleAssignees from './components/multiple_assignees';
import NoAssignee from './components/expanded/no_assignee';
import SingleAssignee from './components/expanded/single_assignee';
import MultipleAssignees from './components/expanded/multiple_assignees';
import CollapsedNoAssignee from './components/collapsed/no_assignee';
import CollapsedSingleAssignee from './components/collapsed/single_assignee';
import CollapsedTwoAssignees from './components/collapsed/two_assignees';
import CollapsedMultipleAssignees from './components/collapsed/multiple_assignees';
import SidebarAssigneesService from './services/sidebar_assignees_service';
import SidebarAssigneesStore from './stores/sidebar_assignees_store';
......@@ -16,22 +21,24 @@ const sidebarAssigneesOptions = () => ({
const element = document.querySelector(selector);
const path = element.dataset.path;
const field = element.dataset.field;
const editable = element.hasAttribute('data-editable');
const currentUserId = parseInt(element.dataset.userId, 10);
const service = new SidebarAssigneesService(path, field);
const assignees = new SidebarAssigneesStore(currentUserId, service);
const assignees = new SidebarAssigneesStore(currentUserId, service, editable);
return {
assignees,
};
},
computed: {
numberOfAssignees() {
return this.assignees.users.length;
},
componentName() {
const numberOfAssignees = this.assignees.users.length;
if (numberOfAssignees === 0) {
if (this.numberOfAssignees === 0) {
return 'no-assignee';
} else if (numberOfAssignees === 1) {
} else if (this.numberOfAssignees === 1) {
return 'single-assignee';
} else {
return 'multiple-assignees';
......@@ -39,18 +46,33 @@ const sidebarAssigneesOptions = () => ({
},
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';
}
},
},
components: {
'no-assignee': NoAssignee,
'single-assignee': SingleAssignee,
'multiple-assignees': MultipleAssignees,
'assignee-title': AssigneeTitle,
'collapsed-single-assignee': CollapsedSingleAssignee,
'collapsed-no-assignee': CollapsedNoAssignee,
'collapsed-two-assignees': CollapsedTwoAssignees,
'collapsed-multiple-assignees': CollapsedMultipleAssignees,
},
template: `
<div class="sidebar-assignees">
<assignee-title :numberOfAssignees="assignees.users.length" :loading="assignees.loading" />
<div>
<component :is="collapsedComponentName" :users="assignees.users" />
<assignee-title :numberOfAssignees="assignees.users.length" :loading="assignees.loading" :editable="assignees.editable"/>
<component class="value" :is="componentName" :assignees="assignees" :class="{ hidden: hideComponent }" />
</div>
`,
......
export default class SidebarAssigneesStore {
constructor(currentUserId, service) {
constructor(currentUserId, service, editable) {
this.currentUserId = currentUserId;
this.service = service;
this.users = [];
this.saved = true;
this.loading = false;
this.editable = editable;
}
addUser(id, name, username, avatarUrl, saved) {
......
......@@ -93,3 +93,14 @@
align-self: center;
}
}
.avatar-counter {
background-color: $gray-darkest;
color: $white-light;
border: 1px solid $white-light;
border-radius: 1em;
font-family: $regular_font;
font-size: 9px;
line-height: 17px;
text-align: center;
}
......@@ -557,14 +557,7 @@
.diff-comments-more-count,
.diff-notes-collapse {
background-color: $gray-darkest;
color: $white-light;
border: 1px solid $white-light;
border-radius: 1em;
font-family: $regular_font;
font-size: 9px;
line-height: 17px;
text-align: center;
@extend .avatar-counter;
}
.diff-notes-collapse {
......
......@@ -324,6 +324,22 @@
color: $gl-text-color;
}
}
&.multiple-users {
display: flex;
}
}
.sidebar-avatar-counter {
width: 25px;
height: 25px;
border-radius: 12px;
line-height: 2.5em;
z-index: 1;
position: relative;
left: inherit;
margin-left: -2px;
margin-top: -1px;
}
.sidebar-collapsed-user {
......@@ -334,6 +350,23 @@
.issuable-header-btn {
display: none;
}
.multiple-users {
.author_link:first-child {
z-index: 2;
.avatar {
margin-left: 6px;
margin-right: 0;
transform: translateX(2px);
}
}
.author_link .avatar {
margin-left: 0;
transform: translateX(-2px);
}
}
}
a {
......
......@@ -24,7 +24,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
.block.assignee
- if issuable.instance_of?(Issue)
#js-vue-sidebar-assignees{ data: { path: issuable_json_path(issuable), field: "#{issuable.to_ability_name}[assignee_id]", user: { id: current_user.id } } }
#js-vue-sidebar-assignees{ data: { path: issuable_json_path(issuable), field: "#{issuable.to_ability_name}[assignee_id]",'editable' => can_edit_issuable ? true : false, user: { id: current_user.id } } }
.title.hide-collapsed
Assignee
= icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
......@@ -212,11 +212,13 @@
= project_ref
= clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
- issuable.assignees.each do |assignee|
:javascript
document.addEventListener('DOMContentLoaded', () => {
gl.sidebarAssigneesOptions.assignees.addUser(parseInt("#{assignee.id}", 10), "#{assignee.name}", "#{assignee.username}", "#{assignee.avatar_url}", true);
});
- if issuable.instance_of?(Issue)
- issuable.assignees.each do |assignee|
:javascript
document.addEventListener('DOMContentLoaded', () => {
gl.sidebarAssigneesOptions.assignees.addUser(parseInt("#{assignee.id}", 10), "#{assignee.name}", "#{assignee.username}", "#{assignee.avatar_url}", true);
});
:javascript
gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}');
......
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