diff options
author | Phil Hughes <me@iamphill.com> | 2017-02-01 15:23:01 +0000 |
---|---|---|
committer | Fatih Acet <acetfatih@gmail.com> | 2017-02-03 17:02:44 +0300 |
commit | 4428bb27b78bf8f75d8ff15c227a8dfbb82aaa8e (patch) | |
tree | d5cfeca79348d875fe6cb4b6de2b94ed0c1d5bc0 /app/assets/javascripts/boards | |
parent | b4113dba0378936024c496b15b3c8a5f1c0a1021 (diff) | |
download | gitlab-ce-4428bb27b78bf8f75d8ff15c227a8dfbb82aaa8e.tar.gz |
Removed Masonry, instead uses groups of data
Added some error handling which reverts the frontend data changes &
notifies the user
Diffstat (limited to 'app/assets/javascripts/boards')
10 files changed, 122 insertions, 76 deletions
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index afe4c9f9175..4ac91786762 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -5,7 +5,6 @@ //= require vue //= require vue-resource //= require Sortable -//= require masonry //= require_tree ./models //= require_tree ./stores //= require_tree ./services diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js.es6 b/app/assets/javascripts/boards/components/issue_card_inner.js.es6 index 10b82ba0998..22a8b971ff8 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js.es6 +++ b/app/assets/javascripts/boards/components/issue_card_inner.js.es6 @@ -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); @@ -55,6 +55,12 @@ Store.updateFiltersUrl(); }, + labelStyle(label) { + return { + backgroundColor: label.color, + color: label.textColor, + }; + }, }, template: ` <div> @@ -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 }} diff --git a/app/assets/javascripts/boards/components/modal/footer.js.es6 b/app/assets/javascripts/boards/components/modal/footer.js.es6 index 8883beb1290..4085a22b25a 100644 --- a/app/assets/javascripts/boards/components/modal/footer.js.es6 +++ b/app/assets/javascripts/boards/components/modal/footer.js.es6 @@ -1,5 +1,7 @@ +/* eslint-disable no-new */ //= require ./lists_dropdown /* global Vue */ +/* global Flash */ (() => { const ModalStore = gl.issueBoards.ModalStore; @@ -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: { @@ -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 diff --git a/app/assets/javascripts/boards/components/modal/header.js.es6 b/app/assets/javascripts/boards/components/modal/header.js.es6 index 194d598d42e..dbbcd73f1fe 100644 --- a/app/assets/javascripts/boards/components/modal/header.js.es6 +++ b/app/assets/javascripts/boards/components/modal/header.js.es6 @@ -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; @@ -16,6 +16,9 @@ return 'Deselect all'; }, + showSearch() { + return this.activeTab === 'all' && !this.loading && this.issuesCount > 0; + }, }, methods: { toggleAll() { @@ -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" diff --git a/app/assets/javascripts/boards/components/modal/index.js.es6 b/app/assets/javascripts/boards/components/modal/index.js.es6 index 43d2fa03d92..666f4e16793 100644 --- a/app/assets/javascripts/boards/components/modal/index.js.es6 +++ b/app/assets/javascripts/boards/components/modal/index.js.es6 @@ -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, @@ -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); }); @@ -75,7 +78,7 @@ this.loadingNewPage = false; if (!this.issuesCount) { - this.issuesCount = this.issues.length; + this.issuesCount = data.size; } }); }, @@ -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, @@ -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 diff --git a/app/assets/javascripts/boards/components/modal/list.js.es6 b/app/assets/javascripts/boards/components/modal/list.js.es6 index ae3e405e70e..d0901219216 100644 --- a/app/assets/javascripts/boards/components/modal/list.js.es6 +++ b/app/assets/javascripts/boards/components/modal/list.js.es6 @@ -1,8 +1,7 @@ /* global Vue */ /* global ListIssue */ -/* global Masonry */ +/* global bp */ (() => { - let listMasonry; const ModalStore = gl.issueBoards.ModalStore; gl.issueBoards.ModalList = Vue.extend({ @@ -21,18 +20,10 @@ }, watch: { activeTab() { - this.initMasonry(); - if (this.activeTab === 'all') { ModalStore.purgeUnselectedIssues(); } }, - issues: { - handler() { - this.initMasonry(); - }, - deep: true, - }, }, computed: { loopIssues() { @@ -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); @@ -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, @@ -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> diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 b/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 index bb2d43c4a21..96f12da3753 100644 --- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 +++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 @@ -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 }"> diff --git a/app/assets/javascripts/boards/components/modal/tabs.js.es6 b/app/assets/javascripts/boards/components/modal/tabs.js.es6 index d556c6d3e04..e8cb43f3503 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.js.es6 +++ b/app/assets/javascripts/boards/components/modal/tabs.js.es6 @@ -23,7 +23,7 @@ href="#" role="button" @click.prevent="changeTab('all')"> - <span>All issues</span> + All issues <span class="badge"> {{ issuesCount }} </span> @@ -34,7 +34,7 @@ href="#" role="button" @click.prevent="changeTab('selected')"> - <span>Selected issues</span> + Selected issues <span class="badge"> {{ selectedCount }} </span> diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 b/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 index 124baaae42a..e74935e1cb0 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 @@ -1,4 +1,6 @@ +/* eslint-disable no-new */ /* global Vue */ +/* global Flash */ (() => { const Store = gl.issueBoards.BoardsStore; @@ -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 = {}; diff --git a/app/assets/javascripts/boards/stores/modal_store.js.es6 b/app/assets/javascripts/boards/stores/modal_store.js.es6 index 9c498ba48c4..fa46b6135e0 100644 --- a/app/assets/javascripts/boards/stores/modal_store.js.es6 +++ b/app/assets/javascripts/boards/stores/modal_store.js.es6 @@ -5,6 +5,7 @@ class ModalStore { constructor() { this.store = { + columns: 3, issues: [], issuesCount: false, selectedIssues: [], @@ -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); |