summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/boards/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/boards/components')
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue18
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue112
-rw-r--r--app/assets/javascripts/boards/components/board_card_layout.vue93
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue63
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue70
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue6
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue27
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue60
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue47
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue30
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js4
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue10
-rw-r--r--app/assets/javascripts/boards/components/issuable_title.vue21
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue24
-rw-r--r--app/assets/javascripts/boards/components/issue_due_date.vue7
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue7
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue24
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue10
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue26
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue25
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue16
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue7
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js3
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue22
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_editable_item.vue79
25 files changed, 469 insertions, 342 deletions
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index afdf0290e8e..55e3e4a6329 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,10 +1,14 @@
<script>
+import { GlButton } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { __ } from '~/locale';
import ListLabel from '~/boards/models/label';
import boardsStore from '../stores/boards_store';
export default {
+ components: {
+ GlButton,
+ },
data() {
return {
predefinedLabels: [
@@ -84,15 +88,17 @@ export default {
)
}}
</p>
- <button
- class="btn btn-success btn-inverted btn-block"
- type="button"
+ <gl-button
+ category="secondary"
+ variant="success"
+ block="block"
+ class="gl-mb-0"
@click.stop="addDefaultLists"
>
{{ s__('BoardBlankState|Add default lists') }}
- </button>
- <button class="btn btn-default btn-block" type="button" @click.stop="clearBlankState">
+ </gl-button>
+ <gl-button category="secondary" variant="default" block="block" @click.stop="clearBlankState">
{{ s__("BoardBlankState|Nevermind, I'll use my own") }}
- </button>
+ </gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 246d3b9dcd1..31050eef83d 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -1,6 +1,5 @@
<script>
-/* eslint-disable vue/require-default-prop */
-import IssueCardInner from './issue_card_inner.vue';
+import BoardCardLayout from './board_card_layout.vue';
import eventHub from '../eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
import boardsStore from '../stores/boards_store';
@@ -8,7 +7,7 @@ import boardsStore from '../stores/boards_store';
export default {
name: 'BoardsIssueCard',
components: {
- IssueCardInner,
+ BoardCardLayout,
},
props: {
list: {
@@ -21,80 +20,29 @@ export default {
default: () => ({}),
required: false,
},
- issueLinkBase: {
- type: String,
- default: '',
- required: false,
- },
- disabled: {
- type: Boolean,
- default: false,
- required: false,
- },
- index: {
- type: Number,
- default: 0,
- required: false,
- },
- rootPath: {
- type: String,
- default: '',
- required: false,
- },
- groupId: {
- type: Number,
- required: false,
- },
- },
- data() {
- return {
- showDetail: false,
- detailIssue: boardsStore.detail,
- multiSelect: boardsStore.multiSelect,
- };
- },
- computed: {
- issueDetailVisible() {
- return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
- },
- multiSelectVisible() {
- return this.multiSelect.list.findIndex(issue => issue.id === this.issue.id) > -1;
- },
- canMultiSelect() {
- return gon.features && gon.features.multiSelectBoard;
- },
},
methods: {
- mouseDown() {
- this.showDetail = true;
+ // These are methods instead of computed's, because boardsStore is not reactive.
+ isActive() {
+ return this.getActiveId() === this.issue.id;
},
- mouseMove() {
- this.showDetail = false;
+ getActiveId() {
+ return boardsStore.detail?.issue?.id;
},
- showIssue(e) {
- if (e.target.classList.contains('js-no-trigger')) return;
-
+ showIssue({ isMultiSelect }) {
// If no issues are opened, close all sidebars first
- if (!boardsStore.detail?.issue?.id) {
+ if (!this.getActiveId()) {
sidebarEventHub.$emit('sidebar.closeAll');
}
+ if (this.isActive()) {
+ eventHub.$emit('clearDetailIssue', isMultiSelect);
- // If CMD or CTRL is clicked
- const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey);
-
- if (this.showDetail || isMultiSelect) {
- this.showDetail = false;
-
- if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) {
- eventHub.$emit('clearDetailIssue', isMultiSelect);
-
- if (isMultiSelect) {
- eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
- }
- } else {
+ if (isMultiSelect) {
eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
- boardsStore.setListDetail(this.list);
}
+ } else {
+ eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
+ boardsStore.setListDetail(this.list);
}
},
},
@@ -102,28 +50,12 @@ export default {
</script>
<template>
- <li
- :class="{
- 'multi-select': multiSelectVisible,
- 'user-can-drag': !disabled && issue.id,
- 'is-disabled': disabled || !issue.id,
- 'is-active': issueDetailVisible,
- }"
- :index="index"
- :data-issue-id="issue.id"
+ <board-card-layout
data-qa-selector="board_card"
- class="board-card p-3 rounded"
- @mousedown="mouseDown"
- @mousemove="mouseMove"
- @mouseup="showIssue($event)"
- >
- <issue-card-inner
- :list="list"
- :issue="issue"
- :issue-link-base="issueLinkBase"
- :group-id="groupId"
- :root-path="rootPath"
- :update-filters="true"
- />
- </li>
+ :issue="issue"
+ :list="list"
+ :is-active="isActive()"
+ v-bind="$attrs"
+ @show="showIssue"
+ />
</template>
diff --git a/app/assets/javascripts/boards/components/board_card_layout.vue b/app/assets/javascripts/boards/components/board_card_layout.vue
new file mode 100644
index 00000000000..072dd87861a
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_card_layout.vue
@@ -0,0 +1,93 @@
+<script>
+import IssueCardInner from './issue_card_inner.vue';
+import boardsStore from '../stores/boards_store';
+
+export default {
+ name: 'BoardsIssueCard',
+ components: {
+ IssueCardInner,
+ },
+ props: {
+ list: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
+ issue: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ index: {
+ type: Number,
+ default: 0,
+ required: false,
+ },
+ isActive: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ showDetail: false,
+ multiSelect: boardsStore.multiSelect,
+ };
+ },
+ computed: {
+ multiSelectVisible() {
+ return this.multiSelect.list.findIndex(issue => issue.id === this.issue.id) > -1;
+ },
+ canMultiSelect() {
+ return gon.features && gon.features.multiSelectBoard;
+ },
+ },
+ methods: {
+ mouseDown() {
+ this.showDetail = true;
+ },
+ mouseMove() {
+ this.showDetail = false;
+ },
+ showIssue(e) {
+ // Don't do anything if this happened on a no trigger element
+ if (e.target.classList.contains('js-no-trigger')) return;
+
+ const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey);
+
+ if (this.showDetail || isMultiSelect) {
+ this.showDetail = false;
+ this.$emit('show', { event: e, isMultiSelect });
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <li
+ :class="{
+ 'multi-select': multiSelectVisible,
+ 'user-can-drag': !disabled && issue.id,
+ 'is-disabled': disabled || !issue.id,
+ 'is-active': isActive,
+ }"
+ :index="index"
+ :data-issue-id="issue.id"
+ :data-issue-iid="issue.iid"
+ :data-issue-path="issue.referencePath"
+ data-testid="board_card"
+ class="board-card p-3 rounded"
+ @mousedown="mouseDown"
+ @mousemove="mouseMove"
+ @mouseup="showIssue($event)"
+ >
+ <issue-card-inner :list="list" :issue="issue" :update-filters="true" />
+ </li>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index dae24338e45..6d216911798 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -1,9 +1,11 @@
<script>
+import { mapGetters, mapActions } from 'vuex';
import Sortable from 'sortablejs';
import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import EmptyComponent from '~/vue_shared/components/empty_component';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import BoardBlankState from './board_blank_state.vue';
import BoardList from './board_list.vue';
import boardsStore from '../stores/boards_store';
@@ -21,7 +23,7 @@ export default {
directives: {
Tooltip,
},
- mixins: [isWipLimitsOn],
+ mixins: [isWipLimitsOn, glFeatureFlagMixin()],
props: {
list: {
type: Object,
@@ -32,27 +34,15 @@ export default {
type: Boolean,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
- boardId: {
- type: String,
- required: true,
- },
canAdminList: {
type: Boolean,
required: false,
default: false,
},
- groupId: {
- type: Number,
- required: false,
- default: null,
+ },
+ inject: {
+ boardId: {
+ type: String,
},
},
data() {
@@ -62,6 +52,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['getIssues']),
showBoardListAndBoardInfo() {
return this.list.type !== ListType.blank && this.list.type !== ListType.promotion;
},
@@ -69,19 +60,36 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
},
+ listIssues() {
+ if (!this.glFeatures.graphqlBoardLists) {
+ return this.list.issues;
+ }
+ return this.getIssues(this.list.id);
+ },
+ shouldFetchIssues() {
+ return this.glFeatures.graphqlBoardLists && this.list.type !== ListType.blank;
+ },
},
watch: {
filter: {
handler() {
- this.list.page = 1;
- this.list.getIssues(true).catch(() => {
- // TODO: handle request error
- });
+ if (this.shouldFetchIssues) {
+ this.fetchIssuesForList(this.list.id);
+ } else {
+ this.list.page = 1;
+ this.list.getIssues(true).catch(() => {
+ // TODO: handle request error
+ });
+ }
},
deep: true,
},
},
mounted() {
+ if (this.shouldFetchIssues) {
+ this.fetchIssuesForList(this.list.id);
+ }
+
const instance = this;
const sortableOptions = getBoardSortableDefaultOptions({
@@ -108,6 +116,7 @@ export default {
Sortable.create(this.$el.parentNode, sortableOptions);
},
methods: {
+ ...mapActions(['fetchIssuesForList']),
showListNewIssueForm(listId) {
eventHub.$emit('showForm', listId);
},
@@ -130,22 +139,14 @@ export default {
<div
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
>
- <board-list-header
- :can-admin-list="canAdminList"
- :list="list"
- :disabled="disabled"
- :board-id="boardId"
- />
+ <board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
<board-list
v-if="showBoardListAndBoardInfo"
ref="board-list"
:disabled="disabled"
- :group-id="groupId || null"
- :issue-link-base="issueLinkBase"
- :issues="list.issues"
+ :issues="listIssues"
:list="list"
:loading="list.loading"
- :root-path="rootPath"
/>
<board-blank-state v-if="canAdminList && list.id === 'blank'" />
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index c42295792f1..c7b3da0e672 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -1,13 +1,15 @@
<script>
-import { mapState } from 'vuex';
+import { mapState, mapGetters, mapActions } from 'vuex';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
-import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
+import { GlAlert } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
BoardColumn,
- EpicsSwimlanes,
+ BoardContentSidebar: () => import('ee_component/boards/components/board_content_sidebar.vue'),
+ EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
+ GlAlert,
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -19,66 +21,58 @@ export default {
type: Boolean,
required: true,
},
- groupId: {
- type: Number,
- required: false,
- default: null,
- },
disabled: {
type: Boolean,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
- boardId: {
- type: String,
- required: true,
- },
},
computed: {
- ...mapState(['isShowingEpicsSwimlanes', 'boardLists']),
- isSwimlanesOn() {
- return this.glFeatures.boardsWithSwimlanes && this.isShowingEpicsSwimlanes;
+ ...mapState(['boardLists', 'error']),
+ ...mapGetters(['isSwimlanesOn']),
+ boardListsToUse() {
+ return this.glFeatures.graphqlBoardLists ? this.boardLists : this.lists;
},
},
+ mounted() {
+ if (this.glFeatures.graphqlBoardLists) {
+ this.fetchLists();
+ this.showPromotionList();
+ }
+ },
+ methods: {
+ ...mapActions(['fetchLists', 'showPromotionList']),
+ },
};
</script>
<template>
<div>
+ <gl-alert v-if="error" variant="danger" :dismissible="false">
+ {{ error }}
+ </gl-alert>
<div
v-if="!isSwimlanesOn"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
data-qa-selector="boards_list"
>
<board-column
- v-for="list in lists"
+ v-for="list in boardListsToUse"
:key="list.id"
ref="board"
:can-admin-list="canAdminList"
- :group-id="groupId"
:list="list"
:disabled="disabled"
- :issue-link-base="issueLinkBase"
- :root-path="rootPath"
- :board-id="boardId"
/>
</div>
- <epics-swimlanes
- v-else
- ref="swimlanes"
- :lists="boardLists"
- :can-admin-list="canAdminList"
- :disabled="disabled"
- :board-id="boardId"
- :group-id="groupId"
- :root-path="rootPath"
- />
+
+ <template v-else>
+ <epics-swimlanes
+ ref="swimlanes"
+ :lists="boardLists"
+ :can-admin-list="canAdminList"
+ :disabled="disabled"
+ />
+ <board-content-sidebar />
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 231059b895e..385dd5fdc71 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -25,11 +25,11 @@ export default {
type: Boolean,
required: true,
},
- milestonePath: {
+ labelsPath: {
type: String,
required: true,
},
- labelsPath: {
+ labelsWebUrl: {
type: String,
required: true,
},
@@ -201,8 +201,8 @@ export default {
:collapse-scope="isNewForm"
:board="board"
:can-admin-board="canAdminBoard"
- :milestone-path="milestonePath"
:labels-path="labelsPath"
+ :labels-web-url="labelsWebUrl"
:enable-scoped-labels="enableScopedLabels"
:project-id="projectId"
:group-id="groupId"
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 1a26782f6f0..25f8ffca633 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -6,6 +6,7 @@ import boardCard from './board_card.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
import { sprintf, __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
getBoardSortableDefaultOptions,
@@ -24,12 +25,8 @@ export default {
boardNewIssue,
GlLoadingIcon,
},
+ mixins: [glFeatureFlagMixin()],
props: {
- groupId: {
- type: Number,
- required: false,
- default: 0,
- },
disabled: {
type: Boolean,
required: true,
@@ -46,14 +43,6 @@ export default {
type: Boolean,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
},
data() {
return {
@@ -83,6 +72,7 @@ export default {
deep: true,
},
issues() {
+ if (this.glFeatures.graphqlBoardLists) return;
this.$nextTick(() => {
if (
this.scrollHeight() <= this.listHeight() &&
@@ -413,6 +403,8 @@ export default {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
+ if (this.glFeatures.graphqlBoardLists) return;
+
if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) {
this.loadNextPage();
}
@@ -430,11 +422,7 @@ export default {
<div v-if="loading" class="board-list-loading text-center" :aria-label="__('Loading issues')">
<gl-loading-icon />
</div>
- <board-new-issue
- v-if="list.type !== 'closed' && showIssueForm"
- :group-id="groupId"
- :list="list"
- />
+ <board-new-issue v-if="list.type !== 'closed' && showIssueForm" :list="list" />
<ul
v-show="!loading"
ref="list"
@@ -450,9 +438,6 @@ export default {
:index="index"
:list="list"
:issue="issue"
- :issue-link-base="issueLinkBase"
- :group-id="groupId"
- :root-path="rootPath"
:disabled="disabled"
/>
<li v-if="showCount" class="board-list-count text-center" data-issue-id="-1">
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index bafe07afb48..361fe252afb 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -1,4 +1,5 @@
<script>
+import { mapActions } from 'vuex';
import {
GlButton,
GlButtonGroup,
@@ -17,6 +18,7 @@ import boardsStore from '../stores/boards_store';
import eventHub from '../eventhub';
import { ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@@ -32,7 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [isWipLimitsOn],
+ mixins: [isWipLimitsOn, glFeatureFlagMixin()],
props: {
list: {
type: Object,
@@ -43,10 +45,6 @@ export default {
type: Boolean,
required: true,
},
- boardId: {
- type: String,
- required: true,
- },
canAdminList: {
type: Boolean,
required: false,
@@ -58,6 +56,11 @@ export default {
default: false,
},
},
+ inject: {
+ boardId: {
+ type: String,
+ },
+ },
data() {
return {
weightFeatureAvailable: false,
@@ -94,10 +97,11 @@ export default {
showAssigneeListDetails() {
return this.list.type === 'assignee' && (this.list.isExpanded || !this.isSwimlanesHeader);
},
+ issuesCount() {
+ return this.list.issuesSize;
+ },
issuesTooltipLabel() {
- const { issuesSize } = this.list;
-
- return n__(`%d issue`, `%d issues`, issuesSize);
+ return n__(`%d issue`, `%d issues`, this.issuesCount);
},
chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
@@ -126,8 +130,12 @@ export default {
collapsedTooltipTitle() {
return this.listTitle || this.listAssignee;
},
+ shouldDisplaySwimlanes() {
+ return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn;
+ },
},
methods: {
+ ...mapActions(['updateList']),
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
@@ -136,20 +144,28 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`);
},
toggleExpanded() {
- if (this.list.isExpandable) {
- this.list.isExpanded = !this.list.isExpanded;
-
- if (AccessorUtilities.isLocalStorageAccessSafe() && !this.isLoggedIn) {
- localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
- }
+ this.list.isExpanded = !this.list.isExpanded;
- if (this.isLoggedIn) {
- this.list.update();
- }
+ if (!this.isLoggedIn) {
+ this.addToLocalStorage();
+ } else {
+ this.updateListFunction();
+ }
- // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
- // Close all tooltips manually to prevent dangling tooltips.
- this.$root.$emit('bv::hide::tooltip');
+ // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
+ // Close all tooltips manually to prevent dangling tooltips.
+ this.$root.$emit('bv::hide::tooltip');
+ },
+ addToLocalStorage() {
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
+ }
+ },
+ updateListFunction() {
+ if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
+ this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
+ } else {
+ this.list.update();
}
},
},
@@ -172,7 +188,7 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
- 'gl-py-3': !list.isExpanded && !isSwimlanesHeader,
+ 'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"
@@ -288,7 +304,7 @@ export default {
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" />
- <issue-count :issues-size="list.issuesSize" :max-issue-count="list.maxIssueCount" />
+ <issue-count :issues-size="issuesCount" :max-issue-count="list.maxIssueCount" />
</span>
<!-- The following is only true in EE. -->
<template v-if="weightFeatureAvailable">
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 34e8438ba4c..348d485ff37 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,11 +1,13 @@
<script>
import $ from 'jquery';
+import { mapActions, mapGetters } from 'vuex';
import { GlButton } from '@gitlab/ui';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import ListIssue from 'ee_else_ce/boards/models/issue';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import boardsStore from '../stores/boards_store';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'BoardNewIssue',
@@ -13,17 +15,18 @@ export default {
ProjectSelect,
GlButton,
},
+ mixins: [glFeatureFlagMixin()],
props: {
- groupId: {
- type: Number,
- required: false,
- default: 0,
- },
list: {
type: Object,
required: true,
},
},
+ inject: {
+ groupId: {
+ type: Number,
+ },
+ },
data() {
return {
title: '',
@@ -32,18 +35,23 @@ export default {
};
},
computed: {
+ ...mapGetters(['isSwimlanesOn']),
disabled() {
if (this.groupId) {
return this.title === '' || !this.selectedProject.name;
}
return this.title === '';
},
+ shouldDisplaySwimlanes() {
+ return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn;
+ },
},
mounted() {
this.$refs.input.focus();
eventHub.$on('setSelectedProject', this.setSelectedProject);
},
methods: {
+ ...mapActions(['addListIssue', 'addListIssueFailure']),
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return Promise.resolve();
@@ -70,21 +78,31 @@ export default {
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
+ if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
+ this.addListIssue({ list: this.list, issue, position: 0 });
+ }
+
return this.list
.newIssue(issue)
.then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
- boardsStore.setIssueDetail(issue);
- boardsStore.setListDetail(this.list);
+ if (!this.shouldDisplaySwimlanes && !this.glFeatures.graphqlBoardLists) {
+ boardsStore.setIssueDetail(issue);
+ boardsStore.setListDetail(this.list);
+ }
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
// Remove the issue
- this.list.removeIssue(issue);
+ if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
+ this.addListIssueFailure({ list: this.list, issue });
+ } else {
+ this.list.removeIssue(issue);
+ }
// Show error message
this.error = true;
@@ -121,7 +139,7 @@ export default {
<project-select v-if="groupId" :group-id="groupId" :list="list" />
<div class="clearfix gl-mt-3">
<gl-button
- ref="submit-button"
+ ref="submitButton"
:disabled="disabled"
class="float-left"
variant="success"
@@ -129,9 +147,14 @@ export default {
type="submit"
>{{ __('Submit issue') }}</gl-button
>
- <gl-button class="float-right" type="button" variant="default" @click="cancel">{{
- __('Cancel')
- }}</gl-button>
+ <gl-button
+ ref="cancelButton"
+ class="float-right"
+ type="button"
+ variant="default"
+ @click="cancel"
+ >{{ __('Cancel') }}</gl-button
+ >
</div>
</form>
</div>
diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
index 3149762ecdf..e2600883e89 100644
--- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
@@ -1,11 +1,12 @@
<script>
import { GlDrawer, GlLabel } from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapState, mapGetters } from 'vuex';
import { __ } from '~/locale';
import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/sidebar/event_hub';
import { isScopedLabel } from '~/lib/utils/common_utils';
-import { inactiveId } from '~/boards/constants';
+import { LIST } from '~/boards/constants';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
// NOTE: need to revisit how we handle headerHeight, because we have so many different header and footer options.
export default {
@@ -23,18 +24,20 @@ export default {
BoardSettingsListTypes: () =>
import('ee_component/boards/components/board_settings_list_types.vue'),
},
+ mixins: [glFeatureFlagMixin()],
computed: {
- ...mapState(['activeId']),
+ ...mapGetters(['isSidebarOpen']),
+ ...mapState(['activeId', 'sidebarType', 'boardLists']),
activeList() {
/*
Warning: Though a computed property it is not reactive because we are
referencing a List Model class. Reactivity only applies to plain JS objects
*/
+ if (this.glFeatures.graphqlBoardLists) {
+ return this.boardLists.find(({ id }) => id === this.activeId);
+ }
return boardsStore.state.lists.find(({ id }) => id === this.activeId);
},
- isSidebarOpen() {
- return this.activeId !== inactiveId;
- },
activeListLabel() {
return this.activeList.label;
},
@@ -44,18 +47,18 @@ export default {
listTypeTitle() {
return this.$options.labelListText;
},
+ showSidebar() {
+ return this.sidebarType === LIST;
+ },
},
created() {
- eventHub.$on('sidebar.closeAll', this.closeSidebar);
+ eventHub.$on('sidebar.closeAll', this.unsetActiveId);
},
beforeDestroy() {
- eventHub.$off('sidebar.closeAll', this.closeSidebar);
+ eventHub.$off('sidebar.closeAll', this.unsetActiveId);
},
methods: {
- ...mapActions(['setActiveId']),
- closeSidebar() {
- this.setActiveId(inactiveId);
- },
+ ...mapActions(['unsetActiveId']),
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
@@ -65,10 +68,11 @@ export default {
<template>
<gl-drawer
+ v-if="showSidebar"
class="js-board-settings-sidebar"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
- @close="closeSidebar"
+ @close="unsetActiveId"
>
<template #header>{{ $options.listSettingsText }}</template>
<template v-if="isSidebarOpen">
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 3790c494085..d26f15c1723 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -83,7 +83,7 @@ export default Vue.extend({
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el)
- .data('glDropdown')
+ .data('deprecatedJQueryDropdown')
.clearMenu();
});
}
@@ -95,7 +95,7 @@ export default Vue.extend({
},
},
created() {
- // Get events from glDropdown
+ // Get events from deprecatedJQueryDropdown
eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
eventHub.$on('sidebar.addAssignee', this.addAssignee);
eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees);
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 48f6ba6cfc7..271e1fc4b5f 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -36,10 +36,6 @@ export default {
type: Object,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
throttleDuration: {
type: Number,
default: 200,
@@ -65,6 +61,10 @@ export default {
type: String,
required: true,
},
+ labelsWebUrl: {
+ type: String,
+ required: true,
+ },
projectId: {
type: Number,
required: true,
@@ -335,8 +335,8 @@ export default {
<board-form
v-if="currentPage"
- :milestone-path="milestonePath"
:labels-path="labelsPath"
+ :labels-web-url="labelsWebUrl"
:project-id="projectId"
:group-id="groupId"
:can-admin-board="canAdminBoard"
diff --git a/app/assets/javascripts/boards/components/issuable_title.vue b/app/assets/javascripts/boards/components/issuable_title.vue
new file mode 100644
index 00000000000..40627a9fab8
--- /dev/null
+++ b/app/assets/javascripts/boards/components/issuable_title.vue
@@ -0,0 +1,21 @@
+<script>
+export default {
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ refPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div data-testid="issue-title">
+ <p class="gl-font-weight-bold">{{ title }}</p>
+ <p class="gl-mb-0">{{ refPath }}</p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index d90928f35b6..8658f51e5cf 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,10 +1,9 @@
<script>
import { sortBy } from 'lodash';
import { mapState } from 'vuex';
-import { GlLabel, GlTooltipDirective } from '@gitlab/ui';
+import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import { sprintf, __ } from '~/locale';
-import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import IssueDueDate from './issue_due_date.vue';
@@ -15,7 +14,7 @@ import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
components: {
GlLabel,
- Icon,
+ GlIcon,
UserAvatarLink,
TooltipOnTruncate,
IssueDueDate,
@@ -31,28 +30,23 @@ export default {
type: Object,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
list: {
type: Object,
required: false,
default: () => ({}),
},
- rootPath: {
- type: String,
- required: true,
- },
updateFilters: {
type: Boolean,
required: false,
default: false,
},
+ },
+ inject: {
groupId: {
type: Number,
- required: false,
- default: null,
+ },
+ rootPath: {
+ type: String,
},
},
data() {
@@ -148,7 +142,7 @@ export default {
<div>
<div class="d-flex board-card-header" dir="auto">
<h4 class="board-card-title gl-mb-0 gl-mt-0">
- <icon
+ <gl-icon
v-if="issue.blocked"
v-gl-tooltip
name="issue-block"
@@ -156,7 +150,7 @@ export default {
class="issue-blocked-icon gl-mr-2"
:aria-label="__('Blocked issue')"
/>
- <icon
+ <gl-icon
v-if="issue.confidential"
v-gl-tooltip
name="eye-slash"
diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue
index 4add5ee646a..fb45de6e14d 100644
--- a/app/assets/javascripts/boards/components/issue_due_date.vue
+++ b/app/assets/javascripts/boards/components/issue_due_date.vue
@@ -1,7 +1,6 @@
<script>
import dateFormat from 'dateformat';
-import { GlTooltip } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlTooltip, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import {
getDayDifference,
@@ -12,7 +11,7 @@ import {
export default {
components: {
- Icon,
+ GlIcon,
GlTooltip,
},
props: {
@@ -87,7 +86,7 @@ export default {
<template>
<span>
<span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
- <icon :class="{ 'text-danger': isPastDue }" class="board-card-info-icon" name="calendar" />
+ <gl-icon :class="{ 'text-danger': isPastDue }" class="board-card-info-icon" name="calendar" />
<time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body
}}</time>
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue
index e8b7689da13..fe56833016e 100644
--- a/app/assets/javascripts/boards/components/issue_time_estimate.vue
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -1,12 +1,11 @@
<script>
-import { GlTooltip } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlTooltip, GlIcon } from '@gitlab/ui';
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import boardsStore from '../stores/boards_store';
export default {
components: {
- Icon,
+ GlIcon,
GlTooltip,
},
props: {
@@ -34,7 +33,7 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
- <icon name="hourglass" class="board-card-info-icon" /><time class="board-card-info-text">{{
+ <gl-icon name="hourglass" class="board-card-info-icon" /><time class="board-card-info-text">{{
timeEstimate
}}</time>
</span>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index 66f59009714..cd4512f320f 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -1,9 +1,14 @@
<script>
+/* eslint-disable vue/no-v-html */
+import { GlButton } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
export default {
+ components: {
+ GlButton,
+ },
mixins: [modalMixin],
props: {
newIssuePath: {
@@ -53,17 +58,22 @@ export default {
<div class="text-content">
<h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p>
- <a v-if="activeTab === 'all'" :href="newIssuePath" class="btn btn-success btn-inverted">{{
- __('New issue')
- }}</a>
- <button
+ <gl-button
+ v-if="activeTab === 'all'"
+ :href="newIssuePath"
+ category="secondary"
+ variant="success"
+ >
+ {{ __('New issue') }}
+ </gl-button>
+ <gl-button
v-if="activeTab === 'selected'"
- class="btn btn-default"
- type="button"
+ category="primary"
+ variant="default"
@click="changeTab('all')"
>
{{ __('Open issues') }}
- </button>
+ </gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index c4953dda793..d28a03da97f 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -1,4 +1,5 @@
<script>
+import { GlButton } from '@gitlab/ui';
import footerEEMixin from 'ee_else_ce/boards/mixins/modal_footer';
import { deprecatedCreateFlash as Flash } from '../../../flash';
import { __, n__ } from '../../../locale';
@@ -10,6 +11,7 @@ import boardsStore from '../../stores/boards_store';
export default {
components: {
ListsDropdown,
+ GlButton,
},
mixins: [modalMixin, footerEEMixin],
data() {
@@ -65,14 +67,14 @@ export default {
<template>
<footer class="form-actions add-issues-footer">
<div class="float-left">
- <button :disabled="submitDisabled" class="btn btn-success" type="button" @click="addIssues">
+ <gl-button :disabled="submitDisabled" category="primary" variant="success" @click="addIssues">
{{ submitText }}
- </button>
+ </gl-button>
<span class="inline add-issues-footer-to-list">{{ __('to list') }}</span>
<lists-dropdown />
</div>
- <button class="btn btn-default float-right" type="button" @click="toggleModal(false)">
+ <gl-button class="float-right" @click="toggleModal(false)">
{{ __('Cancel') }}
- </button>
+ </gl-button>
</footer>
</template>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index 573284d2b44..3e96ecca24c 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -1,5 +1,6 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
+import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import ModalFilters from './filters';
import ModalTabs from './tabs.vue';
@@ -10,6 +11,7 @@ export default {
components: {
ModalTabs,
ModalFilters,
+ GlButton,
},
mixins: [modalMixin],
props: {
@@ -17,10 +19,6 @@ export default {
type: Number,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
labelPath: {
type: String,
required: true,
@@ -43,7 +41,7 @@ export default {
},
methods: {
toggleAll() {
- this.$refs.selectAllBtn.blur();
+ this.$refs.selectAllBtn.$el.blur();
ModalStore.toggleAll();
},
@@ -55,28 +53,28 @@ export default {
<header class="add-issues-header border-top-0 form-actions">
<h2 class="m-0">
Add issues
- <button
- type="button"
+ <gl-button
+ category="tertiary"
+ icon="close"
class="close"
data-dismiss="modal"
:aria-label="__('Close')"
@click="toggleModal(false)"
- >
- <span aria-hidden="true">×</span>
- </button>
+ />
</h2>
</header>
<modal-tabs v-if="!loading && issuesCount > 0" />
<div v-if="showSearch" class="d-flex gl-mb-3">
<modal-filters :store="filter" />
- <button
+ <gl-button
ref="selectAllBtn"
- type="button"
- class="btn btn-success btn-inverted gl-ml-3"
+ category="secondary"
+ variant="success"
+ class="gl-ml-3"
@click="toggleAll"
>
{{ selectAllText }}
- </button>
+ </gl-button>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
index 20344b66140..817b3bdddb0 100644
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ b/app/assets/javascripts/boards/components/modal/index.vue
@@ -26,22 +26,10 @@ export default {
type: String,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
projectId: {
type: Number,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
labelPath: {
type: String,
required: true,
@@ -149,17 +137,8 @@ export default {
class="add-issues-modal d-flex position-fixed position-top-0 position-bottom-0 position-left-0 position-right-0 h-100"
>
<div class="add-issues-container d-flex flex-column m-auto rounded">
- <modal-header
- :project-id="projectId"
- :milestone-path="milestonePath"
- :label-path="labelPath"
- />
- <modal-list
- v-if="!loading && showList && !filterLoading"
- :issue-link-base="issueLinkBase"
- :root-path="rootPath"
- :empty-state-svg="emptyStateSvg"
- />
+ <modal-header :project-id="projectId" :label-path="labelPath" />
+ <modal-list v-if="!loading && showList && !filterLoading" :empty-state-svg="emptyStateSvg" />
<empty-state
v-if="showEmptyState"
:new-issue-path="newIssuePath"
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index 78e3351a79e..219263bd9b9 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -1,23 +1,15 @@
<script>
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlIcon } from '@gitlab/ui';
import ModalStore from '../../stores/modal_store';
import IssueCardInner from '../issue_card_inner.vue';
export default {
components: {
IssueCardInner,
- Icon,
+ GlIcon,
},
props: {
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
emptyStateSvg: {
type: String,
required: true,
@@ -134,8 +126,8 @@ export default {
class="board-card position-relative p-3 rounded"
@click="toggleIssue($event, issue)"
>
- <issue-card-inner :issue="issue" :issue-link-base="issueLinkBase" :root-path="rootPath" />
- <icon
+ <issue-card-inner :issue="issue" />
+ <gl-icon
v-if="issue.selected"
:aria-label="'Issue #' + issue.id + ' selected'"
name="mobile-issue-close"
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 3fbe8fe1be7..fe10e7fb856 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -1,13 +1,12 @@
<script>
-import { GlLink } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlLink, GlIcon } from '@gitlab/ui';
import ModalStore from '../../stores/modal_store';
import boardsStore from '../../stores/boards_store';
export default {
components: {
GlLink,
- Icon,
+ GlIcon,
},
data() {
return {
@@ -29,7 +28,7 @@ export default {
<div class="dropdown inline">
<button class="dropdown-menu-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
<span :style="{ backgroundColor: selected.label.color }" class="dropdown-label-box"> </span>
- {{ selected.title }} <icon name="chevron-down" />
+ {{ selected.title }} <gl-icon name="chevron-down" class="dropdown-menu-toggle-icon" />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 2b9fdf11b37..2e356f1353a 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from '~/flash';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
+import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
$(document)
.off('created.label')
@@ -36,7 +37,7 @@ export default function initNewListDropdown() {
$dropdownToggle.data('projectPath'),
);
- $dropdownToggle.glDropdown({
+ initDeprecatedJQueryDropdown($dropdownToggle, {
data(term, callback) {
axios
.get($dropdownToggle.attr('data-list-labels-path'))
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 598e92726c1..59e7620962a 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -1,30 +1,30 @@
<script>
import $ from 'jquery';
import { escape } from 'lodash';
-import { GlLoadingIcon } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import eventHub from '../eventhub';
import Api from '../../api';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
+import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default {
name: 'BoardProjectSelect',
components: {
- Icon,
+ GlIcon,
GlLoadingIcon,
},
props: {
- groupId: {
- type: Number,
- required: true,
- default: 0,
- },
list: {
type: Object,
required: true,
},
},
+ inject: {
+ groupId: {
+ type: Number,
+ },
+ },
data() {
return {
loading: true,
@@ -37,7 +37,7 @@ export default {
},
},
mounted() {
- $(this.$refs.projectsDropdown).glDropdown({
+ initDeprecatedJQueryDropdown($(this.$refs.projectsDropdown), {
filterable: true,
filterRemote: true,
search: {
@@ -105,13 +105,13 @@ export default {
data-toggle="dropdown"
aria-expanded="false"
>
- {{ selectedProjectName }} <icon name="chevron-down" />
+ {{ selectedProjectName }} <gl-icon name="chevron-down" class="dropdown-menu-toggle-icon" />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
<div class="dropdown-title">{{ __('Projects') }}</div>
<div class="dropdown-input">
<input class="dropdown-input-field" type="search" :placeholder="__('Search projects')" />
- <icon name="search" class="dropdown-input-search" data-hidden="true" />
+ <gl-icon name="search" class="dropdown-input-search" data-hidden="true" />
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><gl-loading-icon /></div>
diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
new file mode 100644
index 00000000000..8df03ea581f
--- /dev/null
+++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
@@ -0,0 +1,79 @@
+<script>
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+
+export default {
+ components: { GlButton, GlLoadingIcon },
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ inject: ['canUpdate'],
+ data() {
+ return {
+ edit: false,
+ };
+ },
+ destroyed() {
+ window.removeEventListener('click', this.collapseWhenOffClick);
+ },
+ methods: {
+ collapseWhenOffClick({ target }) {
+ if (!this.$el.contains(target)) {
+ this.collapse();
+ }
+ },
+ expand() {
+ if (this.edit) {
+ return;
+ }
+
+ this.edit = true;
+ this.$emit('changed', this.edit);
+ window.addEventListener('click', this.collapseWhenOffClick);
+ },
+ collapse() {
+ if (!this.edit) {
+ return;
+ }
+
+ this.edit = false;
+ this.$emit('changed', this.edit);
+ window.removeEventListener('click', this.collapseWhenOffClick);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-display-flex gl-justify-content-space-between gl-mb-3">
+ <span class="gl-vertical-align-middle">
+ <span data-testid="title">{{ title }}</span>
+ <gl-loading-icon v-if="loading" inline class="gl-ml-2" />
+ </span>
+ <gl-button
+ v-if="canUpdate"
+ variant="link"
+ class="gl-text-gray-900!"
+ data-testid="edit-button"
+ @click="expand()"
+ >
+ {{ __('Edit') }}
+ </gl-button>
+ </div>
+ <div v-show="!edit" class="gl-text-gray-400" data-testid="collapsed-content">
+ <slot name="collapsed">{{ __('None') }}</slot>
+ </div>
+ <div v-show="edit" data-testid="expanded-content">
+ <slot></slot>
+ </div>
+ </div>
+</template>