Commit 35caa356 authored by Jacob Schatz's avatar Jacob Schatz
Browse files

Merge branch 'issue-boards-ui' into 'issue-boards'

Issue boards UI

## What does this MR do?

UI for issue boards.

## Are there points in the code the reviewer needs to double check?

## Why was this MR needed?

## What are the relevant issue numbers?

-  Issue: #17907
-  Backend MR: !5548
-  Frontend MR: !5554
- Documentation !5713 

## Screenshots (if relevant)

![Screen_Shot_2016-08-17_at_14.06.06](/uploads/e2abed987d2c2d280e8b8034c8949024/Screen_Shot_2016-08-17_at_14.06.06.png)


See merge request !5554
parents ec106b28 dc550ace
Loading
Loading
Loading
Loading
+57 −0
Original line number Original line Diff line number Diff line
//= require vue
//= require vue-resource
//= require Sortable
//= require_tree ./models
//= require_tree ./stores
//= require_tree ./services
//= require_tree ./mixins
//= require ./components/board
//= require ./components/new_list_dropdown
//= require ./vue_resource_interceptor

$(() => {
  const $boardApp = document.getElementById('board-app'),
        Store = gl.issueBoards.BoardsStore;

  window.gl = window.gl || {};

  if (gl.IssueBoardsApp) {
    gl.IssueBoardsApp.$destroy(true);
  }

  gl.IssueBoardsApp = new Vue({
    el: $boardApp,
    components: {
      'board': gl.issueBoards.Board
    },
    data: {
      state: Store.state,
      loading: true,
      endpoint: $boardApp.dataset.endpoint,
      disabled: $boardApp.dataset.disabled === 'true',
      issueLinkBase: $boardApp.dataset.issueLinkBase
    },
    init: Store.create.bind(Store),
    created () {
      gl.boardService = new BoardService(this.endpoint);
    },
    ready () {
      Store.disabled = this.disabled;
      gl.boardService.all()
        .then((resp) => {          
          resp.json().forEach((board) => {
            const list = Store.addList(board);

            if (list.type === 'done') {
              list.position = Infinity;
            } else if (list.type === 'backlog') {
              list.position = -1;
            }
          });

          Store.addBlankState();
          this.loading = false;
        });
    }
  });
});
+85 −0
Original line number Original line Diff line number Diff line
//= require ./board_blank_state
//= require ./board_delete
//= require ./board_list

(() => {
  const Store = gl.issueBoards.BoardsStore;

  window.gl = window.gl || {};
  window.gl.issueBoards = window.gl.issueBoards || {};

  gl.issueBoards.Board = Vue.extend({
    components: {
      'board-list': gl.issueBoards.BoardList,
      'board-delete': gl.issueBoards.BoardDelete,
      'board-blank-state': gl.issueBoards.BoardBlankState
    },
    props: {
      list: Object,
      disabled: Boolean,
      issueLinkBase: String
    },
    data () {
      return {
        query: '',
        filters: Store.state.filters
      };
    },
    watch: {
      query () {
        this.list.filters = this.getFilterData();
        this.list.getIssues(true);
      },
      filters: {
        handler () {
          this.list.page = 1;
          this.list.getIssues(true);
        },
        deep: true
      }
    },
    methods: {
      getFilterData () {
        const filters = this.filters;
        let queryData = { search: this.query };

        Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });

        return queryData;
      }
    },
    ready () {
      const options = gl.issueBoards.getBoardSortableDefaultOptions({
        disabled: this.disabled,
        group: 'boards',
        draggable: '.is-draggable',
        handle: '.js-board-handle',
        onEnd: (e) => {
          document.body.classList.remove('is-dragging');

          if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
            const order = this.sortable.toArray(),
                  $board = this.$parent.$refs.board[e.oldIndex + 1],
                  list = $board.list;

            $board.$destroy(true);

            this.$nextTick(() => {
              Store.state.lists.splice(e.newIndex, 0, list);
              Store.moveList(list, order);
            });
          }
        }
      });

      if (bp.getBreakpointSize() === 'xs') {
        options.handle = '.js-board-drag-handle';
      }

      this.sortable = Sortable.create(this.$el.parentNode, options);
    },
    beforeDestroy () {
      Store.state.lists.$remove(this.list);
    }
  });
})();
+49 −0
Original line number Original line Diff line number Diff line
(() => {
  const Store = gl.issueBoards.BoardsStore;

  window.gl = window.gl || {};
  window.gl.issueBoards = window.gl.issueBoards || {};

  gl.issueBoards.BoardBlankState = Vue.extend({
    data () {
      return {
        predefinedLabels: [
          new ListLabel({ title: 'Development', color: '#5CB85C' }),
          new ListLabel({ title: 'Testing', color: '#F0AD4E' }),
          new ListLabel({ title: 'Production', color: '#FF5F00' }),
          new ListLabel({ title: 'Ready', color: '#FF0000' })
        ]
      }
    },
    methods: {
      addDefaultLists () {
        this.clearBlankState();

        this.predefinedLabels.forEach((label, i) => {
          Store.addList({
            title: label.title,
            position: i,
            list_type: 'label',
            label: {
              title: label.title,
              color: label.color
            }
          });
        });

        // Save the labels
        gl.boardService.generateDefaultLists()
          .then((resp) => {
            resp.json().forEach((listObj) => {
              const list = Store.findList('title', listObj.title);

              list.id = listObj.id;
              list.label.id = listObj.label.id;
              list.getIssues();
            });
          });
      },
      clearBlankState: Store.removeBlankState.bind(Store)
    }
  });
})();
+43 −0
Original line number Original line Diff line number Diff line
(() => {
  const Store = gl.issueBoards.BoardsStore;

  window.gl = window.gl || {};
  window.gl.issueBoards = window.gl.issueBoards || {};

  gl.issueBoards.BoardCard = Vue.extend({
    props: {
      list: Object,
      issue: Object,
      issueLinkBase: String,
      disabled: Boolean,
      index: Number
    },
    methods: {
      filterByLabel (label, e) {
        let labelToggleText = label.title;
        const labelIndex = Store.state.filters['label_name'].indexOf(label.title);
        $(e.target).tooltip('hide');

        if (labelIndex === -1) {
          Store.state.filters['label_name'].push(label.title);
          $('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
        } else {
          Store.state.filters['label_name'].splice(labelIndex, 1);
          labelToggleText = Store.state.filters['label_name'][0];
          $(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
        }

        const selectedLabels = Store.state.filters['label_name'];
        if (selectedLabels.length === 0) {
          labelToggleText = 'Label';
        } else if (selectedLabels.length > 1) {
          labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
        }

        $('.labels-filter .dropdown-toggle-text').text(labelToggleText);

        Store.updateFiltersUrl();
      }
    }
  });
})();
+19 −0
Original line number Original line Diff line number Diff line
(() => {
  window.gl = window.gl || {};
  window.gl.issueBoards = window.gl.issueBoards || {};

  gl.issueBoards.BoardDelete = Vue.extend({
    props: {
      list: Object
    },
    methods: {
      deleteBoard () {
        $(this.$el).tooltip('hide');

        if (confirm('Are you sure you want to delete this list?')) {
          this.list.destroy();
        }
      }
    }
  });
})();
Loading