From f452c1aa7ddb13958502422769dc20a9a8e160fa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 31 May 2017 12:34:54 +0100 Subject: Expand/collapse close & backlog lists in issue boards The closed & backlog lists in issue boards are no collapsible. They can be collapsed independently of each other & this selection is then saved to the browser through localStorage. When the page loads, the code gets the data from localStorage & determines whether to show or hide the list Closes #23917 --- app/assets/javascripts/boards/components/board.js | 20 ++++++++++- app/assets/javascripts/boards/models/list.js | 4 ++- .../javascripts/boards/stores/boards_store.js | 8 +++-- app/assets/stylesheets/pages/boards.scss | 42 ++++++++++++++++++++++ .../projects/boards/lists_controller.rb | 2 ++ app/models/board.rb | 4 +++ app/models/list.rb | 2 +- app/services/boards/create_service.rb | 1 + app/services/boards/issues/list_service.rb | 2 +- app/views/projects/boards/_show.html.haml | 1 + .../projects/boards/components/_board.html.haml | 11 +++--- .../expand-backlog-closed-lists-issue-boards.yml | 4 +++ 12 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/expand-backlog-closed-lists-issue-boards.yml diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 9ba84489910..06c28baf5cb 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -1,6 +1,7 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var */ /* global Sortable */ import Vue from 'vue'; +import AccessorUtilities from '../../lib/utils/accessor'; import boardList from './board_list'; import boardBlankState from './board_blank_state'; import './board_delete'; @@ -22,6 +23,10 @@ gl.issueBoards.Board = Vue.extend({ disabled: Boolean, issueLinkBase: String, rootPath: String, + boardId: { + type: String, + required: true, + }, }, data () { return { @@ -78,7 +83,13 @@ gl.issueBoards.Board = Vue.extend({ methods: { showNewIssueForm() { this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm; - } + }, + toggleExpanded(e) { + if (this.list.isExpandable && !e.target.classList.contains('js-no-trigger-collapse')) { + this.list.isExpanded = !this.list.isExpanded; + localStorage.setItem(`boards.${this.boardId}.${this.list.type}.expanded`, this.list.isExpanded); + } + }, }, mounted () { this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({ @@ -102,4 +113,11 @@ gl.issueBoards.Board = Vue.extend({ this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions); }, + created() { + if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) { + const isCollapsed = localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false'; + + this.list.isExpanded = !isCollapsed; + } + }, }); diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 90561d0f7a8..3591b2917d8 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -12,7 +12,9 @@ class List { this.position = obj.position; this.title = obj.title; this.type = obj.list_type; - this.preset = ['closed', 'blank'].indexOf(this.type) > -1; + this.preset = ['backlog', 'closed', 'blank'].indexOf(this.type) > -1; + this.isExpandable = ['backlog', 'closed'].indexOf(this.type) > -1; + this.isExpanded = true; this.page = 1; this.loading = true; this.loadingMore = false; diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index ad9997ac334..e58a0af2242 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -31,10 +31,14 @@ gl.issueBoards.BoardsStore = { }, new (listObj) { const list = this.addList(listObj); + const backlogList = this.findList('type', 'backlog', 'backlog'); list .save() .then(() => { + // Remove any new issues from the backlog + // as they will be visible in the new list + list.issues.forEach(backlogList.removeIssue.bind(backlogList)); this.state.lists = _.sortBy(this.state.lists, 'position'); }) .catch(() => { @@ -47,7 +51,7 @@ gl.issueBoards.BoardsStore = { }, shouldAddBlankState () { // Decide whether to add the blank state - return !(this.state.lists.filter(list => list.type !== 'closed')[0]); + return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'done')[0]); }, addBlankState () { if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; @@ -100,7 +104,7 @@ gl.issueBoards.BoardsStore = { issueTo.removeLabel(listFrom.label); } - if (listTo.type === 'closed') { + if (listTo.type === 'closed' && listFrom.type !== 'backlog') { issueLists.forEach((list) => { list.removeIssue(issue); }); diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index ebe662136d5..64bd2ee10fa 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -96,9 +96,51 @@ @media (min-width: $screen-sm-min) { width: 400px; } + + &.is-expandable { + .board-header { + cursor: pointer; + } + } + + &.is-collapsed { + width: 60px; + + .board-header { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .board-title { + position: initial; + padding: 0; + border-bottom: 0; + + > span { + display: block; + transform: rotate(90deg) translate(25px, 0); + } + } + + .board-title-expandable-toggle { + position: absolute; + top: 50%; + left: 50%; + margin-left: -10px; + } + + .board-list-component, + .board-issue-count-holder { + display: none; + } + } } .board-inner { + position: relative; height: 100%; font-size: $issue-boards-font-size; background: $gray-light; diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb index 67e3c9add81..fbfabd86575 100644 --- a/app/controllers/projects/boards/lists_controller.rb +++ b/app/controllers/projects/boards/lists_controller.rb @@ -5,6 +5,8 @@ module Projects before_action :authorize_read_list!, only: [:index] def index + board.lists.create(list_type: :backlog) unless board.lists.backlog.any? + render json: serialize_as_json(board.lists) end diff --git a/app/models/board.rb b/app/models/board.rb index cf8317891b5..18081a32157 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -5,6 +5,10 @@ class Board < ActiveRecord::Base validates :project, presence: true + def backlog_list + lists.merge(List.backlog).take + end + def closed_list lists.merge(List.closed).take end diff --git a/app/models/list.rb b/app/models/list.rb index fbd19acd1f5..e587f6d99a6 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -2,7 +2,7 @@ class List < ActiveRecord::Base belongs_to :board belongs_to :label - enum list_type: { label: 1, closed: 2 } + enum list_type: { backlog: 0, label: 1, closed: 2 } validates :board, :list_type, presence: true validates :label, :position, presence: true, if: :label? diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index fd9ff115eab..68f6a8619e5 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -12,6 +12,7 @@ module Boards def create_board! board = project.boards.create + board.lists.create(list_type: :backlog) board.lists.create(list_type: :closed) board diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 533e6787855..418fa9afd6e 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -3,7 +3,7 @@ module Boards class ListService < BaseService def execute issues = IssuesFinder.new(current_user, filter_params).execute - issues = without_board_labels(issues) unless list + issues = without_board_labels(issues) unless movable_list? issues = with_list_label(issues) if movable_list? issues.order_by_position_and_priority end diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index efec69662f3..6684ecfce81 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -26,6 +26,7 @@ ":disabled" => "disabled", ":issue-link-base" => "issueLinkBase", ":root-path" => "rootPath", + ":board-id" => "boardId", ":key" => "_uid" } = render "projects/boards/components/sidebar" %board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'), diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index bc5c727bf0d..37939e6266d 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -1,8 +1,11 @@ -.board{ ":class" => '{ "is-draggable": !list.preset }', +.board{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded }', ":data-id" => "list.id" } .board-inner - %header.board-header{ ":class" => '{ "has-border": list.label }', ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } + %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" } %h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' } + %i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable", + ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-left\": !list.isExpanded }", + "aria-hidden": "true" } %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")', data: { container: "body", placement: "bottom" } } {{ list.title }} @@ -10,13 +13,13 @@ %span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } {{ list.issuesSize }} - if can?(current_user, :admin_issue, @project) - %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", + %button.btn.btn-small.btn-default.pull-right.has-tooltip.js-no-trigger-collapse{ type: "button", "@click" => "showNewIssueForm", "v-if" => 'list.type !== "closed"', "aria-label" => "New issue", "title" => "New issue", data: { placement: "top", container: "body" } } - = icon("plus") + = icon("plus", class: "js-no-trigger-collapse") - if can?(current_user, :admin_list, @project) %board-delete{ "inline-template" => true, ":list" => "list", diff --git a/changelogs/unreleased/expand-backlog-closed-lists-issue-boards.yml b/changelogs/unreleased/expand-backlog-closed-lists-issue-boards.yml new file mode 100644 index 00000000000..4796f8e918b --- /dev/null +++ b/changelogs/unreleased/expand-backlog-closed-lists-issue-boards.yml @@ -0,0 +1,4 @@ +--- +title: Expand/collapse backlog & closed lists in issue boards +merge_request: +author: -- cgit v1.2.1