Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-08 00:09:26 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-08 00:09:26 +0300
commit17c8111494f51e79744c782db023804f5e4a7410 (patch)
tree8aebe53b8aea72f9d4abac1bf9131203302a5b6e /app/assets/javascripts/milestones
parent4b7575da97d88ef4c7b2ec599b0c3fc457b4f561 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/milestones')
-rw-r--r--app/assets/javascripts/milestones/project_milestone_combobox.vue228
1 files changed, 228 insertions, 0 deletions
diff --git a/app/assets/javascripts/milestones/project_milestone_combobox.vue b/app/assets/javascripts/milestones/project_milestone_combobox.vue
new file mode 100644
index 00000000000..19148d6184f
--- /dev/null
+++ b/app/assets/javascripts/milestones/project_milestone_combobox.vue
@@ -0,0 +1,228 @@
+<script>
+import {
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownHeader,
+ GlNewDropdownItem,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ GlIcon,
+} from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+import Api from '~/api';
+import createFlash from '~/flash';
+import { intersection, debounce } from 'lodash';
+
+export default {
+ components: {
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownHeader,
+ GlNewDropdownItem,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ GlIcon,
+ },
+ model: {
+ prop: 'preselectedMilestones',
+ event: 'change',
+ },
+ props: {
+ projectId: {
+ type: String,
+ required: true,
+ },
+ preselectedMilestones: {
+ type: Array,
+ default: () => [],
+ required: false,
+ },
+ extraLinks: {
+ type: Array,
+ default: () => [],
+ required: false,
+ },
+ },
+ data() {
+ return {
+ searchQuery: '',
+ projectMilestones: [],
+ searchResults: [],
+ selectedMilestones: [],
+ requestCount: 0,
+ };
+ },
+ translations: {
+ milestone: __('Milestone'),
+ selectMilestone: __('Select milestone'),
+ noMilestone: __('No milestone'),
+ noResultsLabel: __('No matching results'),
+ searchMilestones: __('Search Milestones'),
+ },
+ computed: {
+ selectedMilestonesLabel() {
+ if (this.milestoneTitles.length === 1) {
+ return this.milestoneTitles[0];
+ }
+
+ if (this.milestoneTitles.length > 1) {
+ const firstMilestoneName = this.milestoneTitles[0];
+ const numberOfOtherMilestones = this.milestoneTitles.length - 1;
+ return sprintf(__('%{firstMilestoneName} + %{numberOfOtherMilestones} more'), {
+ firstMilestoneName,
+ numberOfOtherMilestones,
+ });
+ }
+
+ return this.$options.translations.noMilestone;
+ },
+ milestoneTitles() {
+ return this.preselectedMilestones.map(milestone => milestone.title);
+ },
+ dropdownItems() {
+ return this.searchResults.length ? this.searchResults : this.projectMilestones;
+ },
+ noResults() {
+ return this.searchQuery.length > 2 && this.searchResults.length === 0;
+ },
+ isLoading() {
+ return this.requestCount !== 0;
+ },
+ },
+ mounted() {
+ this.fetchMilestones();
+ },
+ methods: {
+ fetchMilestones() {
+ this.requestCount += 1;
+
+ Api.projectMilestones(this.projectId)
+ .then(({ data }) => {
+ this.projectMilestones = this.getTitles(data);
+ this.selectedMilestones = intersection(this.projectMilestones, this.milestoneTitles);
+ })
+ .catch(() => {
+ createFlash(__('An error occurred while loading milestones'));
+ })
+ .finally(() => {
+ this.requestCount -= 1;
+ });
+ },
+ searchMilestones: debounce(function searchMilestones() {
+ this.requestCount += 1;
+ const options = {
+ search: this.searchQuery,
+ scope: 'milestones',
+ };
+
+ if (this.searchQuery.length < 3) {
+ this.requestCount -= 1;
+ this.searchResults = [];
+ return;
+ }
+
+ Api.projectSearch(this.projectId, options)
+ .then(({ data }) => {
+ const searchResults = this.getTitles(data);
+
+ this.searchResults = searchResults.length ? searchResults : [];
+ })
+ .catch(() => {
+ createFlash(__('An error occurred while searching for milestones'));
+ })
+ .finally(() => {
+ this.requestCount -= 1;
+ });
+ }, 100),
+ toggleMilestoneSelection(clickedMilestone) {
+ if (!clickedMilestone) return [];
+
+ let milestones = [...this.preselectedMilestones];
+ const hasMilestone = this.milestoneTitles.includes(clickedMilestone);
+
+ if (hasMilestone) {
+ milestones = milestones.filter(({ title }) => title !== clickedMilestone);
+ } else {
+ milestones.push({ title: clickedMilestone });
+ }
+
+ return milestones;
+ },
+ onMilestoneClicked(clickedMilestone) {
+ const milestones = this.toggleMilestoneSelection(clickedMilestone);
+ this.$emit('change', milestones);
+
+ this.selectedMilestones = intersection(
+ this.projectMilestones,
+ milestones.map(milestone => milestone.title),
+ );
+ },
+ isSelectedMilestone(milestoneTitle) {
+ return this.selectedMilestones.includes(milestoneTitle);
+ },
+ getTitles(milestones) {
+ return milestones.filter(({ state }) => state === 'active').map(({ title }) => title);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-new-dropdown>
+ <template slot="button-content">
+ <span ref="buttonText" class="flex-grow-1 ml-1 text-muted">{{
+ selectedMilestonesLabel
+ }}</span>
+ <gl-icon name="chevron-down" />
+ </template>
+
+ <gl-new-dropdown-header>
+ <span class="text-center d-block">{{ $options.translations.selectMilestone }}</span>
+ </gl-new-dropdown-header>
+
+ <gl-new-dropdown-divider />
+
+ <gl-search-box-by-type
+ v-model.trim="searchQuery"
+ class="m-2"
+ :placeholder="this.$options.translations.searchMilestones"
+ @input="searchMilestones"
+ />
+
+ <gl-new-dropdown-item @click="onMilestoneClicked(null)">
+ <span :class="{ 'pl-4': true, 'selected-item': selectedMilestones.length === 0 }">
+ {{ $options.translations.noMilestone }}
+ </span>
+ </gl-new-dropdown-item>
+
+ <gl-new-dropdown-divider />
+
+ <template v-if="isLoading">
+ <gl-loading-icon />
+ <gl-new-dropdown-divider />
+ </template>
+ <template v-else-if="noResults">
+ <div class="dropdown-item-space">
+ <span ref="noResults" class="pl-4">{{ $options.translations.noResultsLabel }}</span>
+ </div>
+ <gl-new-dropdown-divider />
+ </template>
+ <template v-else-if="dropdownItems.length">
+ <gl-new-dropdown-item
+ v-for="item in dropdownItems"
+ :key="item"
+ role="milestone option"
+ @click="onMilestoneClicked(item)"
+ >
+ <span :class="{ 'pl-4': true, 'selected-item': isSelectedMilestone(item) }">
+ {{ item }}
+ </span>
+ </gl-new-dropdown-item>
+ <gl-new-dropdown-divider />
+ </template>
+
+ <gl-new-dropdown-item v-for="(item, idx) in extraLinks" :key="idx" :href="item.url">
+ <span class="pl-4">{{ item.text }}</span>
+ </gl-new-dropdown-item>
+ </gl-new-dropdown>
+</template>