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

[skip ci] add collapsed sidebar assignees

parent 29ffdb55
...@@ -145,7 +145,7 @@ ...@@ -145,7 +145,7 @@
Sidebar.prototype.openDropdown = function(blockOrName) { Sidebar.prototype.openDropdown = function(blockOrName) {
var $block; var $block;
$block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName; $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
$block.find('.edit-link').trigger('click'); $block.find('.edit-link:not(.hidden)').trigger('click');
if (!this.isOpen()) { if (!this.isOpen()) {
this.setCollapseAfterUpdate($block); this.setCollapseAfterUpdate($block);
return this.toggleSidebar('open'); return this.toggleSidebar('open');
......
...@@ -262,6 +262,10 @@ ...@@ -262,6 +262,10 @@
} }
$selectbox.hide(); $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 // display:block overrides the hide-collapse rule
return $value.css('display', ''); return $value.css('display', '');
}, },
......
...@@ -3,6 +3,7 @@ export default { ...@@ -3,6 +3,7 @@ export default {
props: { props: {
loading: { type: Boolean, required: true }, loading: { type: Boolean, required: true },
numberOfAssignees: { type: Number, required: true }, numberOfAssignees: { type: Number, required: true },
editable: { type: Boolean, required: true},
}, },
computed: { computed: {
hasMultipleAssignees() { hasMultipleAssignees() {
...@@ -18,7 +19,7 @@ export default { ...@@ -18,7 +19,7 @@ export default {
Assignee Assignee
</template> </template>
<i aria-hidden="true" class="fa fa-spinner fa-spin block-loading" :class="{ hidden: !loading }"></i> <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> </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 Vue from 'vue';
import AssigneeTitle from './components/assignee_title'; import AssigneeTitle from './components/assignee_title';
import NoAssignee from './components/no_assignee'; import NoAssignee from './components/expanded/no_assignee';
import SingleAssignee from './components/single_assignee'; import SingleAssignee from './components/expanded/single_assignee';
import MultipleAssignees from './components/multiple_assignees'; 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 SidebarAssigneesService from './services/sidebar_assignees_service';
import SidebarAssigneesStore from './stores/sidebar_assignees_store'; import SidebarAssigneesStore from './stores/sidebar_assignees_store';
...@@ -16,22 +21,24 @@ const sidebarAssigneesOptions = () => ({ ...@@ -16,22 +21,24 @@ const sidebarAssigneesOptions = () => ({
const element = document.querySelector(selector); const element = document.querySelector(selector);
const path = element.dataset.path; const path = element.dataset.path;
const field = element.dataset.field; const field = element.dataset.field;
const editable = element.hasAttribute('data-editable');
const currentUserId = parseInt(element.dataset.userId, 10); const currentUserId = parseInt(element.dataset.userId, 10);
const service = new SidebarAssigneesService(path, field); const service = new SidebarAssigneesService(path, field);
const assignees = new SidebarAssigneesStore(currentUserId, service); const assignees = new SidebarAssigneesStore(currentUserId, service, editable);
return { return {
assignees, assignees,
}; };
}, },
computed: { computed: {
numberOfAssignees() {
return this.assignees.users.length;
},
componentName() { componentName() {
const numberOfAssignees = this.assignees.users.length; if (this.numberOfAssignees === 0) {
if (numberOfAssignees === 0) {
return 'no-assignee'; return 'no-assignee';
} else if (numberOfAssignees === 1) { } else if (this.numberOfAssignees === 1) {
return 'single-assignee'; return 'single-assignee';
} else { } else {
return 'multiple-assignees'; return 'multiple-assignees';
...@@ -39,18 +46,33 @@ const sidebarAssigneesOptions = () => ({ ...@@ -39,18 +46,33 @@ const sidebarAssigneesOptions = () => ({
}, },
hideComponent() { hideComponent() {
return !this.assignees.saved; 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: { components: {
'no-assignee': NoAssignee, 'no-assignee': NoAssignee,
'single-assignee': SingleAssignee, 'single-assignee': SingleAssignee,
'multiple-assignees': MultipleAssignees, 'multiple-assignees': MultipleAssignees,
'assignee-title': AssigneeTitle, 'assignee-title': AssigneeTitle,
'collapsed-single-assignee': CollapsedSingleAssignee,
'collapsed-no-assignee': CollapsedNoAssignee,
'collapsed-two-assignees': CollapsedTwoAssignees,
'collapsed-multiple-assignees': CollapsedMultipleAssignees,
}, },
template: ` template: `
<div class="sidebar-assignees"> <div>
<assignee-title :numberOfAssignees="assignees.users.length" :loading="assignees.loading" /> <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 }" /> <component class="value" :is="componentName" :assignees="assignees" :class="{ hidden: hideComponent }" />
</div> </div>
`, `,
......
export default class SidebarAssigneesStore { export default class SidebarAssigneesStore {
constructor(currentUserId, service) { constructor(currentUserId, service, editable) {
this.currentUserId = currentUserId; this.currentUserId = currentUserId;
this.service = service; this.service = service;
this.users = []; this.users = [];
this.saved = true; this.saved = true;
this.loading = false; this.loading = false;
this.editable = editable;
} }
addUser(id, name, username, avatarUrl, saved) { addUser(id, name, username, avatarUrl, saved) {
......
...@@ -93,3 +93,14 @@ ...@@ -93,3 +93,14 @@
align-self: center; 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 @@ ...@@ -557,14 +557,7 @@
.diff-comments-more-count, .diff-comments-more-count,
.diff-notes-collapse { .diff-notes-collapse {
background-color: $gray-darkest; @extend .avatar-counter;
color: $white-light;
border: 1px solid $white-light;
border-radius: 1em;
font-family: $regular_font;
font-size: 9px;
line-height: 17px;
text-align: center;
} }
.diff-notes-collapse { .diff-notes-collapse {
......
...@@ -324,6 +324,22 @@ ...@@ -324,6 +324,22 @@
color: $gl-text-color; 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 { .sidebar-collapsed-user {
...@@ -334,6 +350,23 @@ ...@@ -334,6 +350,23 @@
.issuable-header-btn { .issuable-header-btn {
display: none; 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 { a {
......
...@@ -24,7 +24,7 @@ ...@@ -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| = 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 .block.assignee
- if issuable.instance_of?(Issue) - 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 .title.hide-collapsed
Assignee Assignee
= icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true') = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
...@@ -212,11 +212,13 @@ ...@@ -212,11 +212,13 @@
= project_ref = project_ref
= clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left") = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
- issuable.assignees.each do |assignee|
:javascript - if issuable.instance_of?(Issue)
document.addEventListener('DOMContentLoaded', () => { - issuable.assignees.each do |assignee|
gl.sidebarAssigneesOptions.assignees.addUser(parseInt("#{assignee.id}", 10), "#{assignee.name}", "#{assignee.username}", "#{assignee.avatar_url}", true); :javascript
}); document.addEventListener('DOMContentLoaded', () => {
gl.sidebarAssigneesOptions.assignees.addUser(parseInt("#{assignee.id}", 10), "#{assignee.name}", "#{assignee.username}", "#{assignee.avatar_url}", true);
});
:javascript :javascript
gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}'); 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