Skip to content
Snippets Groups Projects
Commit 4428bb27 authored by Phil Hughes's avatar Phil Hughes Committed by Fatih Acet
Browse files

Removed Masonry, instead uses groups of data

Added some error handling which reverts the frontend data changes &
notifies the user
parent b4113dba
No related branches found
No related tags found
No related merge requests found
Showing
with 154 additions and 2551 deletions
Loading
Loading
@@ -5,7 +5,6 @@
//= require vue
//= require vue-resource
//= require Sortable
//= require masonry
//= require_tree ./models
//= require_tree ./stores
//= require_tree ./services
Loading
Loading
Loading
Loading
@@ -33,7 +33,7 @@
filterByLabel(label, e) {
let labelToggleText = label.title;
const labelIndex = Store.state.filters.label_name.indexOf(label.title);
$(e.target).tooltip('hide');
$(e.currentTarget).tooltip('hide');
 
if (labelIndex === -1) {
Store.state.filters.label_name.push(label.title);
Loading
Loading
@@ -55,6 +55,12 @@
 
Store.updateFiltersUrl();
},
labelStyle(label) {
return {
backgroundColor: label.color,
color: label.textColor,
};
},
},
template: `
<div>
Loading
Loading
@@ -93,7 +99,7 @@
type="button"
v-if="showLabel(label)"
@click="filterByLabel(label, $event)"
:style="{ backgroundColor: label.color, color: label.textColor }"
:style="labelStyle(label)"
:title="label.description"
data-container="body">
{{ label.title }}
Loading
Loading
/* eslint-disable no-new */
//= require ./lists_dropdown
/* global Vue */
/* global Flash */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
 
Loading
Loading
@@ -15,7 +17,7 @@
submitText() {
const count = ModalStore.selectedCount();
 
return `Add ${count > 0 ? count : ''} issue${count > 1 || !count ? 's' : ''}`;
return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
},
},
methods: {
Loading
Loading
@@ -27,6 +29,13 @@
// Post the data to the backend
gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id],
}).catch(() => {
new Flash('Failed to update issues, please try again.', 'alert');
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
list.issuesSize -= 1;
});
});
 
// Add the issues on the frontend
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@
(() => {
const ModalStore = gl.issueBoards.ModalStore;
 
gl.issueBoards.IssuesModalHeader = Vue.extend({
gl.issueBoards.ModalHeader = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return ModalStore.store;
Loading
Loading
@@ -16,6 +16,9 @@
 
return 'Deselect all';
},
showSearch() {
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
},
},
methods: {
toggleAll() {
Loading
Loading
@@ -45,7 +48,7 @@
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
<div
class="add-issues-search append-bottom-10"
v-if="activeTab == 'all' && !loading && issuesCount > 0">
v-if="showSearch">
<input
placeholder="Search issues..."
class="form-control"
Loading
Loading
Loading
Loading
@@ -53,10 +53,9 @@
},
methods: {
searchOperation: _.debounce(function searchOperationDebounce() {
this.issues = [];
this.loadIssues();
this.loadIssues(true);
}, 500),
loadIssues() {
loadIssues(clearIssues = false) {
return gl.boardService.getBacklog({
search: this.searchTerm,
page: this.page,
Loading
Loading
@@ -64,10 +63,14 @@
}).then((res) => {
const data = res.json();
 
if (clearIssues) {
this.issues = [];
}
data.issues.forEach((issueObj) => {
const issue = new ListIssue(issueObj);
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
issue.selected = foundSelectedIssue !== undefined;
issue.selected = !!foundSelectedIssue;
 
this.issues.push(issue);
});
Loading
Loading
@@ -75,7 +78,7 @@
this.loadingNewPage = false;
 
if (!this.issuesCount) {
this.issuesCount = this.issues.length;
this.issuesCount = data.size;
}
});
},
Loading
Loading
@@ -88,9 +91,16 @@
 
return this.issuesCount > 0;
},
showEmptyState() {
if (!this.loading && this.issuesCount === 0) {
return true;
}
return this.activeTab === 'selected' && this.selectedIssues.length === 0;
},
},
components: {
'modal-header': gl.issueBoards.IssuesModalHeader,
'modal-header': gl.issueBoards.ModalHeader,
'modal-list': gl.issueBoards.ModalList,
'modal-footer': gl.issueBoards.ModalFooter,
'empty-state': gl.issueBoards.ModalEmptyState,
Loading
Loading
@@ -106,7 +116,7 @@
:root-path="rootPath"
v-if="!loading && showList"></modal-list>
<empty-state
v-if="(!loading && issuesCount === 0) || (activeTab === 'selected' && selectedIssues.length === 0)"
v-if="showEmptyState"
:image="blankStateImage"
:new-issue-path="newIssuePath"></empty-state>
<section
Loading
Loading
/* global Vue */
/* global ListIssue */
/* global Masonry */
/* global bp */
(() => {
let listMasonry;
const ModalStore = gl.issueBoards.ModalStore;
 
gl.issueBoards.ModalList = Vue.extend({
Loading
Loading
@@ -21,18 +20,10 @@
},
watch: {
activeTab() {
this.initMasonry();
if (this.activeTab === 'all') {
ModalStore.purgeUnselectedIssues();
}
},
issues: {
handler() {
this.initMasonry();
},
deep: true,
},
},
computed: {
loopIssues() {
Loading
Loading
@@ -42,8 +33,31 @@
 
return this.selectedIssues;
},
groupedIssues() {
const groups = [];
this.loopIssues.forEach((issue, i) => {
const index = i % this.columns;
if (!groups[index]) {
groups.push([]);
}
groups[index].push(issue);
});
return groups;
},
},
methods: {
scrollHandler() {
const currentPage = Math.floor(this.issues.length / this.perPage);
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
&& currentPage === this.page) {
this.loadingNewPage = true;
this.page += 1;
}
},
toggleIssue(e, issue) {
if (e.target.tagName !== 'A') {
ModalStore.toggleIssue(issue);
Loading
Loading
@@ -65,40 +79,29 @@
 
return index !== -1;
},
initMasonry() {
const listScrollTop = this.$refs.list.scrollTop;
this.$nextTick(() => {
this.destroyMasonry();
listMasonry = new Masonry(this.$refs.list, {
transitionDuration: 0,
});
setColumnCount() {
const breakpoint = bp.getBreakpointSize();
 
this.$refs.list.scrollTop = listScrollTop;
});
},
destroyMasonry() {
if (listMasonry) {
listMasonry.destroy();
listMasonry = undefined;
if (breakpoint === 'lg' || breakpoint === 'md') {
this.columns = 3;
} else if (breakpoint === 'sm') {
this.columns = 2;
} else {
this.columns = 1;
}
},
},
mounted() {
this.initMasonry();
this.$refs.list.onscroll = () => {
const currentPage = Math.floor(this.issues.length / this.perPage);
this.scrollHandlerWrapper = this.scrollHandler.bind(this);
this.setColumnCountWrapper = this.setColumnCount.bind(this);
this.setColumnCount();
 
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
&& currentPage === this.page) {
this.loadingNewPage = true;
this.page += 1;
}
};
this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
window.addEventListener('resize', this.setColumnCountWrapper);
},
destroyed() {
this.destroyMasonry();
beforeDestroy() {
this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
window.removeEventListener('resize', this.setColumnCountWrapper);
},
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
Loading
Loading
@@ -108,25 +111,29 @@
class="add-issues-list add-issues-list-columns"
ref="list">
<div
v-for="issue in loopIssues"
v-if="showIssue(issue)"
class="card-parent">
v-for="group in groupedIssues"
class="add-issues-list-column">
<div
class="card"
:class="{ 'is-active': issue.selected }"
@click="toggleIssue($event, issue)">
<issue-card-inner
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath">
</issue-card-inner>
<span
:aria-label="'Issue #' + issue.id + ' selected'"
aria-checked="true"
v-if="issue.selected"
class="issue-card-selected text-center">
<i class="fa fa-check"></i>
</span>
v-for="issue in group"
v-if="showIssue(issue)"
class="card-parent">
<div
class="card"
:class="{ 'is-active': issue.selected }"
@click="toggleIssue($event, issue)">
<issue-card-inner
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath">
</issue-card-inner>
<span
:aria-label="'Issue #' + issue.id + ' selected'"
aria-checked="true"
v-if="issue.selected"
class="issue-card-selected text-center">
<i class="fa fa-check"></i>
</span>
</div>
</div>
</div>
</section>
Loading
Loading
Loading
Loading
@@ -37,7 +37,7 @@
href="#"
role="button"
:class="{ 'is-active': list.id == selected.id }"
@click="modal.selectedList = list">
@click.prevent="modal.selectedList = list">
<span
class="dropdown-label-box"
:style="{ backgroundColor: list.label.color }">
Loading
Loading
Loading
Loading
@@ -23,7 +23,7 @@
href="#"
role="button"
@click.prevent="changeTab('all')">
<span>All issues</span>
All issues
<span class="badge">
{{ issuesCount }}
</span>
Loading
Loading
@@ -34,7 +34,7 @@
href="#"
role="button"
@click.prevent="changeTab('selected')">
<span>Selected issues</span>
Selected issues
<span class="badge">
{{ selectedCount }}
</span>
Loading
Loading
/* eslint-disable no-new */
/* global Vue */
/* global Flash */
(() => {
const Store = gl.issueBoards.BoardsStore;
 
Loading
Loading
@@ -18,17 +20,24 @@
},
methods: {
removeIssue() {
const lists = this.issue.getLists();
const issue = this.issue;
const lists = issue.getLists();
const labelIds = lists.map(list => list.label.id);
 
// Post the remove data
gl.boardService.bulkUpdate([this.issue.globalId], {
gl.boardService.bulkUpdate([issue.globalId], {
remove_label_ids: labelIds,
}).catch(() => {
new Flash('Failed to remove issue from board, please try again.', 'alert');
lists.forEach((list) => {
list.addIssue(issue);
});
});
 
// Remove from the frontend store
lists.forEach((list) => {
list.removeIssue(this.issue);
list.removeIssue(issue);
});
 
Store.detail.issue = {};
Loading
Loading
Loading
Loading
@@ -5,6 +5,7 @@
class ModalStore {
constructor() {
this.store = {
columns: 3,
issues: [],
issuesCount: false,
selectedIssues: [],
Loading
Loading
@@ -25,9 +26,11 @@
 
toggleIssue(issueObj) {
const issue = issueObj;
issue.selected = !issue.selected;
const selected = issue.selected;
 
if (issue.selected) {
issue.selected = !selected;
if (!selected) {
this.addSelectedIssue(issue);
} else {
this.removeSelectedIssue(issue);
Loading
Loading
Loading
Loading
@@ -161,6 +161,9 @@
gl.text.humanize = function(string) {
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
};
gl.text.pluralize = function(str, count) {
return str + (count > 1 || count === 0 ? 's' : '');
};
return gl.text.truncate = function(string, maxLength) {
return string.substr(0, (maxLength - 3)) + '...';
};
Loading
Loading
Loading
Loading
@@ -418,6 +418,18 @@
display: flex;
}
 
.add-issues-list-column {
width: 100%;
@media (min-width: $screen-sm-min) {
width: 50%;
}
@media (min-width: $screen-md-min) {
width: (100% / 3);
}
}
.add-issues-list {
display: -webkit-flex;
display: flex;
Loading
Loading
@@ -429,16 +441,7 @@
overflow-y: scroll;
 
.card-parent {
width: 100%;
padding: 0 5px 5px;
@media (min-width: $screen-sm-min) {
width: 50%;
}
@media (min-width: $screen-md-min) {
width: (100% / 3);
}
}
 
.card {
Loading
Loading
@@ -480,6 +483,6 @@
color: $white-light;
border: 1px solid $border-blue-light;
font-size: 9px;
line-height: 17px;
line-height: 15px;
border-radius: 50%;
}
Loading
Loading
@@ -20,7 +20,7 @@ describe('Issue model', () => {
let issue;
 
beforeEach(() => {
gl.boardService = new BoardService('/test/issue-boards/board', '1');
gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
gl.issueBoards.BoardsStore.create();
 
issue = new ListIssue({
Loading
Loading
Loading
Loading
@@ -24,7 +24,7 @@ describe('List model', () => {
 
beforeEach(() => {
Vue.http.interceptors.push(boardsMockInterceptor);
gl.boardService = new BoardService('/test/issue-boards/board', '1');
gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
gl.issueBoards.BoardsStore.create();
 
list = new List(listObj);
Loading
Loading
Loading
Loading
@@ -21,5 +21,19 @@
expect(largeFont > regular).toBe(true);
});
});
describe('gl.text.pluralize', () => {
it('returns pluralized', () => {
expect(gl.text.pluralize('test', 2)).toBe('tests');
});
it('returns pluralized', () => {
expect(gl.text.pluralize('test', 0)).toBe('tests');
});
it('does not return pluralized', () => {
expect(gl.text.pluralize('test', 1)).toBe('test');
});
});
});
})();
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment