Commit 98d83965 authored by Clement Ho's avatar Clement Ho

[skip ci] refactor

parent e43d79b0
...@@ -2,21 +2,26 @@ export default { ...@@ -2,21 +2,26 @@ export default {
name: 'MultipleAssignees', name: 'MultipleAssignees',
data() { data() {
return { return {
defaultRenderCount: 5,
showLess: true, showLess: true,
}; };
}, },
props: { props: {
store: { rootPath: {
type: Object, type: String,
required: true,
},
users: {
type: Array,
required: true, required: true,
}, },
}, },
computed: { computed: {
renderShowMoreSection() { renderShowMoreSection() {
return this.store.users.length > this.store.defaultRenderCount; return this.users.length > this.defaultRenderCount;
}, },
numberOfHiddenAssignees() { numberOfHiddenAssignees() {
return this.store.users.length - this.store.defaultRenderCount; return this.users.length - this.defaultRenderCount;
}, },
isHiddenAssignees() { isHiddenAssignees() {
return this.numberOfHiddenAssignees > 0; return this.numberOfHiddenAssignees > 0;
...@@ -27,10 +32,10 @@ export default { ...@@ -27,10 +32,10 @@ export default {
this.showLess = !this.showLess; this.showLess = !this.showLess;
}, },
renderAssignee(index) { renderAssignee(index) {
return !this.showLess || (index < this.store.defaultRenderCount && this.showLess); return !this.showLess || (index < this.defaultRenderCount && this.showLess);
}, },
assigneeUrl(username) { assigneeUrl(username) {
return `${this.store.rootPath}${username}`; return `${this.rootPath}${username}`;
}, },
assigneeAlt(name) { assigneeAlt(name) {
return `${name}'s avatar`; return `${name}'s avatar`;
...@@ -40,7 +45,7 @@ export default { ...@@ -40,7 +45,7 @@ export default {
<div class="hide-collapsed"> <div class="hide-collapsed">
<div class="hide-collapsed"> <div class="hide-collapsed">
<div class="user-list"> <div class="user-list">
<div class="user-item" v-for="(user, index) in store.users" <div class="user-item" v-for="(user, index) in users"
v-if="renderAssignee(index)" > v-if="renderAssignee(index)" >
<a class="user-link has-tooltip" <a class="user-link has-tooltip"
data-placement="bottom" data-placement="bottom"
......
import eventHub from '../../event_hub';
export default { export default {
name: 'NoAssignee', name: 'NoAssignee',
props: {
store: {
type: Object,
required: true,
},
},
methods: { methods: {
assignSelf() { assignSelf() {
this.store.addCurrentUser(); eventHub.$emit('addCurrentUser');
}, },
}, },
template: ` template: `
......
export default { export default {
name: 'SingleAssignee', name: 'SingleAssignee',
props: { props: {
store: { rootPath: {
type: Object, type: String,
required: true, required: true,
}, },
user: {
type: Object,
required: true,
}
}, },
computed: { computed: {
user() {
return this.store.users[0];
},
userUrl() { userUrl() {
return `${this.store.rootPath}${this.user.username}`; return `${this.rootPath}${this.user.username}`;
}, },
username() { username() {
return `@${this.user.username}`; return `@${this.user.username}`;
......
import Vue from 'vue';
export default new Vue();
import Vue from 'vue'; import Vue from 'vue';
import sidebarAssigneesOptions from './sidebar_assignees_options';
import AssigneeTitle from './components/assignee_title';
import NoAssignee from './components/expanded/no_assignee';
import SingleAssignee from './components/expanded/single_assignee';
import MultipleAssignees from './components/expanded/multiple_assignees';
import CollapsedAssignees from './components/collapsed/assignees';
import SidebarAssigneesService from './services/sidebar_assignees_service';
import SidebarAssigneesStore from './stores/sidebar_assignees_store';
const sidebarAssigneesOptions = () => ({
el: '#js-vue-sidebar-assignees',
name: 'SidebarAssignees',
data() {
const selector = this.$options.el;
const element = document.querySelector(selector);
// Get data from data attributes passed from haml
const rootPath = element.dataset.rootPath;
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 store = new SidebarAssigneesStore({
currentUserId,
service,
rootPath,
editable,
});
return {
store,
};
},
computed: {
numberOfAssignees() {
return this.store.users.length;
},
componentName() {
switch (this.numberOfAssignees) {
case 0:
return 'no-assignee';
case 1:
return 'single-assignee';
default:
return 'multiple-assignees';
}
},
hideComponent() {
return !this.store.saved;
},
},
components: {
'no-assignee': NoAssignee,
'single-assignee': SingleAssignee,
'multiple-assignees': MultipleAssignees,
'assignee-title': AssigneeTitle,
'collapsed-assignees': CollapsedAssignees,
},
template: `
<div>
<assignee-title
:numberOfAssignees="store.users.length"
:loading="store.loading"
:editable="store.editable"
/>
<collapsed-assignees :users="store.users"/>
<component v-if="store.saved"
class="value"
:is="componentName"
:store="store"
/>
</div>
`,
});
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Expose this to window so that we can add assignees from glDropdown window.gl.sidebarAssigneesOptions = new Vue(sidebarAssigneesOptions);
window.gl.sidebarAssigneesOptions = new Vue(sidebarAssigneesOptions());
}); });
import Vue from 'vue';
import eventHub from './event_hub';
import AssigneeTitle from './components/assignee_title';
import NoAssignee from './components/expanded/no_assignee';
import SingleAssignee from './components/expanded/single_assignee';
import MultipleAssignees from './components/expanded/multiple_assignees';
import CollapsedAssignees from './components/collapsed/assignees';
import SidebarAssigneesService from './services/sidebar_assignees_service';
import SidebarAssigneesStore from './stores/sidebar_assignees_store';
export default {
el: '#js-vue-sidebar-assignees',
name: 'SidebarAssignees',
data() {
const selector = this.$options.el;
const element = document.querySelector(selector);
// Get data from data attributes passed from haml
const rootPath = element.dataset.rootPath;
const path = element.dataset.path;
const field = element.dataset.field;
const editable = element.hasAttribute('data-editable');
const currentUser = {
id: parseInt(element.dataset.userId, 10),
name: element.dataset.userName,
username: element.dataset.userUserName,
avatarUrl: element.dataset.avatar_url,
};
const service = new SidebarAssigneesService(path, field);
const store = new SidebarAssigneesStore({
currentUser,
rootPath,
editable,
assignees: gl.sidebarAssigneesData,
});
return {
store,
service,
loading: false,
};
},
computed: {
numberOfAssignees() {
return this.store.users.length;
},
},
created() {
eventHub.$on('addCurrentUser', this.addCurrentUser);
eventHub.$on('addUser', this.store.addUser.bind(this.store));
eventHub.$on('removeUser', this.store.removeUser.bind(this.store));
eventHub.$on('removeAllUsers', this.store.removeAllUsers.bind(this.store));
eventHub.$on('saveUsers', this.saveUsers);
},
methods: {
addCurrentUser() {
this.store.addCurrentUser();
this.saveUsers();
},
saveUsers() {
this.loading = true;
this.service.update(this.store.getUserIds())
.then((response) => {
this.loading = false;
this.store.saveUsers(response.data.assignees);
}).catch(() => {
this.loading = false;
return new Flash('An error occured while saving assignees', 'alert');
});
},
},
components: {
'no-assignee': NoAssignee,
'single-assignee': SingleAssignee,
'multiple-assignees': MultipleAssignees,
'assignee-title': AssigneeTitle,
'collapsed-assignees': CollapsedAssignees,
},
template: `
<div>
<assignee-title
:numberOfAssignees="store.users.length"
:loading="loading"
:editable="store.editable"
/>
<collapsed-assignees :users="store.users"/>
<div class="value" v-if="!loading">
<no-assignee v-if="numberOfAssignees === 0" />
<single-assignee
v-else-if="numberOfAssignees === 1"
:rootPath="store.rootPath"
:user="store.users[0]"
/>
<multiple-assignees
v-else
:rootPath="store.rootPath"
:users="store.users"
/>
</div>
</div>
`,
};
...@@ -3,19 +3,19 @@ import '~/flash'; ...@@ -3,19 +3,19 @@ import '~/flash';
export default class SidebarAssigneesStore { export default class SidebarAssigneesStore {
constructor(store) { constructor(store) {
const { currentUserId, service, rootPath, editable } = store; const { currentUser, assignees, rootPath, editable } = store;
this.currentUserId = currentUserId; this.currentUser = currentUser;
this.service = service;
this.rootPath = rootPath; this.rootPath = rootPath;
this.users = []; this.users = [];
this.saved = true;
this.loading = false; this.loading = false;
this.editable = editable; this.editable = editable;
this.defaultRenderCount = 5; this.defaultRenderCount = 5;
assignees.forEach(a => this.addUser(this.destructUser(a)));
} }
addUser(user, saved = false) { addUser(user) {
const { id, name, username, avatarUrl } = user; const { id, name, username, avatarUrl } = user;
this.users.push({ this.users.push({
...@@ -24,48 +24,47 @@ export default class SidebarAssigneesStore { ...@@ -24,48 +24,47 @@ export default class SidebarAssigneesStore {
username, username,
avatarUrl, avatarUrl,
}); });
console.log(`addUser()`);
// !saved means that this user was added to UI but not service console.log(user);
this.saved = saved;
} }
addCurrentUser() { addCurrentUser() {
this.addUser({ this.addUser(this.currentUser);
id: this.currentUserId,
});
this.saveUsers();
} }
removeUser(id) { removeUser(id) {
this.saved = false; console.log(`removeUser()`);
console.log(id);
this.users = this.users.filter(u => u.id !== id); this.users = this.users.filter(u => u.id !== id);
} }
saveUsers() { removeAllUsers() {
this.users = [];
}
getUserIds() {
console.log(`getUserIds`);
const ids = this.users.map(u => u.id); 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; if (ids.length > 0 && ids[0] == undefined) {
this.service.update(payload) debugger
.then((response) => { }
const data = response.data; // If there are no ids, that means we have to unassign (which is id = 0)
const assignees = data.assignees; return ids.length > 0 ? ids : [0];
}
this.users = []; destructUser(u) {
return {
id: u.id,
name: u.name,
username: u.username,
avatarUrl: u.avatar_url,
};
}
assignees.forEach(a => this.addUser({ saveUsers(assignees) {
id: a.id, this.users = [];
name: a.name,
username: a.username,
avatarUrl: a.avatar_url,
}, true));
this.saved = true; assignees.forEach(a => this.addUser(this.destructUser(a)));
this.loading = false;
}).catch(() => {
this.loading = false;
return new Flash('An error occured while saving assignees', 'alert');
});
} }
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
/* global ListUser */ /* global ListUser */
import Vue from 'vue'; import Vue from 'vue';
import eventHub from './sidebar_assignees/event_hub';
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
...@@ -250,7 +251,8 @@ import Vue from 'vue'; ...@@ -250,7 +251,8 @@ import Vue from 'vue';
defaultLabel: defaultLabel, defaultLabel: defaultLabel,
hidden: function(e) { hidden: function(e) {
if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-multiselect')) {
gl.sidebarAssigneesOptions.store.saveUsers(); // gl.sidebarAssigneesOptions.store.saveUsers();
eventHub.$emit('saveUsers');
} }
$selectbox.hide(); $selectbox.hide();
...@@ -274,12 +276,12 @@ import Vue from 'vue'; ...@@ -274,12 +276,12 @@ import Vue from 'vue';
// Unassigned selected // Unassigned selected
previouslySelected.each((index, element) => { previouslySelected.each((index, element) => {
const id = parseInt(element.value, 10); const id = parseInt(element.value, 10);
gl.sidebarAssigneesOptions.store.removeUser(id);
element.remove(); element.remove();
}); });
eventHub.$emit('removeAllUsers');
} else if (isActive) { } else if (isActive) {
// user selected // user selected
gl.sidebarAssigneesOptions.store.addUser({ eventHub.$emit('addUser', {
id: user.id, id: user.id,
name: user.name, name: user.name,
username: user.username, username: user.username,
...@@ -292,7 +294,6 @@ import Vue from 'vue'; ...@@ -292,7 +294,6 @@ import Vue from 'vue';
if (unassignedSelected) { if (unassignedSelected) {
unassignedSelected.remove(); unassignedSelected.remove();
gl.sidebarAssigneesOptions.store.removeUser(unassignedSelected);
} }
} else { } else {
if (previouslySelected.length === 0) { if (previouslySelected.length === 0) {
...@@ -301,7 +302,7 @@ import Vue from 'vue'; ...@@ -301,7 +302,7 @@ import Vue from 'vue';
} }
// User unselected // User unselected
gl.sidebarAssigneesOptions.store.removeUser(user.id); eventHub.$emit('removeUser', user.id);
} }
} }
......
...@@ -157,7 +157,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -157,7 +157,7 @@ class Projects::IssuesController < Projects::ApplicationController
if @issue.valid? if @issue.valid?
render json: @issue.to_json(methods: [:task_status, :task_status_short], render json: @issue.to_json(methods: [:task_status, :task_status_short],
include: { milestone: {}, include: { milestone: {},
assignees: { only: [:name, :username], methods: [:avatar_url] }, assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
labels: { methods: :text_color } }) labels: { methods: :text_color } })
else else
render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity
......
...@@ -24,14 +24,24 @@ ...@@ -24,14 +24,24 @@
= 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_ids]",'editable' => can_edit_issuable ? true : false, user: { id: current_user.id }, root: { path: root_path } } } #js-vue-sidebar-assignees{ data: { path: issuable_json_path(issuable), field: "#{issuable.to_ability_name}[assignee_ids]",'editable' => can_edit_issuable ? true : false, user: { id: current_user.id }, name: current_user.name, username: current_user.username, avatar_url: current_user.avatar_url, root: { path: root_path } } }
.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')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('vue_sidebar_assignees') = page_specific_javascript_bundle_tag('sidebar_assignees')
:javascript
gl.sidebarAssigneesData = [];
- issuable.assignees.each do |assignee|
:javascript
gl.sidebarAssigneesData.push({
id: #{assignee.id},
name: "#{assignee.name}",
username: "#{assignee.username}",
avatar_url: "#{assignee.avatar_url}"
})
- else - else
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) } .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) }
- if issuable.assignee - if issuable.assignee
...@@ -220,19 +230,6 @@ ...@@ -220,19 +230,6 @@
= 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")
- if issuable.instance_of?(Issue)
- issuable.assignees.each do |assignee|
:javascript
document.addEventListener('DOMContentLoaded', () => {
gl.sidebarAssigneesOptions.store.addUser({
id: parseInt("#{assignee.id}", 10),
name: "#{assignee.name}",
username: "#{assignee.username}",
avatarUrl: "#{assignee.avatar_url}"
}, true);
});
:javascript :javascript
gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}'); gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}');
new gl.IssuableTimeTracking("#{escape_javascript(serialize_issuable(issuable))}"); new gl.IssuableTimeTracking("#{escape_javascript(serialize_issuable(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