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

[skip ci] refactor

parent e43d79b0
......@@ -2,21 +2,26 @@ export default {
name: 'MultipleAssignees',
data() {
return {
defaultRenderCount: 5,
showLess: true,
};
},
props: {
store: {
type: Object,
rootPath: {
type: String,
required: true,
},
users: {
type: Array,
required: true,
},
},
computed: {
renderShowMoreSection() {
return this.store.users.length > this.store.defaultRenderCount;
return this.users.length > this.defaultRenderCount;
},
numberOfHiddenAssignees() {
return this.store.users.length - this.store.defaultRenderCount;
return this.users.length - this.defaultRenderCount;
},
isHiddenAssignees() {
return this.numberOfHiddenAssignees > 0;
......@@ -27,10 +32,10 @@ export default {
this.showLess = !this.showLess;
},
renderAssignee(index) {
return !this.showLess || (index < this.store.defaultRenderCount && this.showLess);
return !this.showLess || (index < this.defaultRenderCount && this.showLess);
},
assigneeUrl(username) {
return `${this.store.rootPath}${username}`;
return `${this.rootPath}${username}`;
},
assigneeAlt(name) {
return `${name}'s avatar`;
......@@ -40,7 +45,7 @@ export default {
<div class="hide-collapsed">
<div class="hide-collapsed">
<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)" >
<a class="user-link has-tooltip"
data-placement="bottom"
......
import eventHub from '../../event_hub';
export default {
name: 'NoAssignee',
props: {
store: {
type: Object,
required: true,
},
},
methods: {
assignSelf() {
this.store.addCurrentUser();
eventHub.$emit('addCurrentUser');
},
},
template: `
......
export default {
name: 'SingleAssignee',
props: {
store: {
type: Object,
rootPath: {
type: String,
required: true,
},
user: {
type: Object,
required: true,
}
},
computed: {
user() {
return this.store.users[0];
},
userUrl() {
return `${this.store.rootPath}${this.user.username}`;
return `${this.rootPath}${this.user.username}`;
},
username() {
return `@${this.user.username}`;
......
import Vue from 'vue';
export default new Vue();
import Vue from 'vue';
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>
`,
});
import sidebarAssigneesOptions from './sidebar_assignees_options';
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';
export default class SidebarAssigneesStore {
constructor(store) {
const { currentUserId, service, rootPath, editable } = store;
const { currentUser, assignees, rootPath, editable } = store;
this.currentUserId = currentUserId;
this.service = service;
this.currentUser = currentUser;
this.rootPath = rootPath;
this.users = [];
this.saved = true;
this.loading = false;
this.editable = editable;
this.defaultRenderCount = 5;
assignees.forEach(a => this.addUser(this.destructUser(a)));
}
addUser(user, saved = false) {
addUser(user) {
const { id, name, username, avatarUrl } = user;
this.users.push({
......@@ -24,48 +24,47 @@ export default class SidebarAssigneesStore {
username,
avatarUrl,
});
// !saved means that this user was added to UI but not service
this.saved = saved;
console.log(`addUser()`);
console.log(user);
}
addCurrentUser() {
this.addUser({
id: this.currentUserId,
});
this.saveUsers();
this.addUser(this.currentUser);
}
removeUser(id) {
this.saved = false;
console.log(`removeUser()`);
console.log(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);
// 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(payload)
.then((response) => {
const data = response.data;
const assignees = data.assignees;
if (ids.length > 0 && ids[0] == undefined) {
debugger
}
// If there are no ids, that means we have to unassign (which is id = 0)
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({
id: a.id,
name: a.name,
username: a.username,
avatarUrl: a.avatar_url,
}, true));
saveUsers(assignees) {
this.users = [];
this.saved = true;
this.loading = false;
}).catch(() => {
this.loading = false;
return new Flash('An error occured while saving assignees', 'alert');
});
assignees.forEach(a => this.addUser(this.destructUser(a)));
}
}
......@@ -3,6 +3,7 @@
/* global ListUser */
import Vue from 'vue';
import eventHub from './sidebar_assignees/event_hub';
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
......@@ -250,7 +251,8 @@ import Vue from 'vue';
defaultLabel: defaultLabel,
hidden: function(e) {
if ($dropdown.hasClass('js-multiselect')) {
gl.sidebarAssigneesOptions.store.saveUsers();
// gl.sidebarAssigneesOptions.store.saveUsers();
eventHub.$emit('saveUsers');
}
$selectbox.hide();
......@@ -274,12 +276,12 @@ import Vue from 'vue';
// Unassigned selected
previouslySelected.each((index, element) => {
const id = parseInt(element.value, 10);
gl.sidebarAssigneesOptions.store.removeUser(id);
element.remove();
});
eventHub.$emit('removeAllUsers');
} else if (isActive) {
// user selected
gl.sidebarAssigneesOptions.store.addUser({
eventHub.$emit('addUser', {
id: user.id,
name: user.name,
username: user.username,
......@@ -292,7 +294,6 @@ import Vue from 'vue';
if (unassignedSelected) {
unassignedSelected.remove();
gl.sidebarAssigneesOptions.store.removeUser(unassignedSelected);
}
} else {
if (previouslySelected.length === 0) {
......@@ -301,7 +302,7 @@ import Vue from 'vue';
}
// User unselected
gl.sidebarAssigneesOptions.store.removeUser(user.id);
eventHub.$emit('removeUser', user.id);
}
}
......
......@@ -157,7 +157,7 @@ class Projects::IssuesController < Projects::ApplicationController
if @issue.valid?
render json: @issue.to_json(methods: [:task_status, :task_status_short],
include: { milestone: {},
assignees: { only: [:name, :username], methods: [:avatar_url] },
assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
labels: { methods: :text_color } })
else
render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity
......
......@@ -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|
.block.assignee
- 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
Assignee
= icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
- 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
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) }
- if issuable.assignee
......@@ -220,19 +230,6 @@
= project_ref
= 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
gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(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