2021-06-02 15:09:59 +00:00
|
|
|
<script>
|
2021-09-03 12:09:03 +00:00
|
|
|
import { GlDropdownForm, GlDropdownItem, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
|
2021-06-02 15:09:59 +00:00
|
|
|
import fuzzaldrinPlus from 'fuzzaldrin-plus';
|
2021-08-12 12:11:05 +00:00
|
|
|
import { debounce } from 'lodash';
|
|
|
|
import createFlash from '~/flash';
|
|
|
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
|
|
|
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
|
|
|
import { __ } from '~/locale';
|
|
|
|
import projectLabelsQuery from './graphql/project_labels.query.graphql';
|
2021-06-02 15:09:59 +00:00
|
|
|
import LabelItem from './label_item.vue';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
components: {
|
2021-09-03 12:09:03 +00:00
|
|
|
GlDropdownForm,
|
|
|
|
GlDropdownItem,
|
2021-06-02 15:09:59 +00:00
|
|
|
GlLoadingIcon,
|
|
|
|
GlSearchBoxByType,
|
|
|
|
LabelItem,
|
|
|
|
},
|
2021-09-03 12:09:03 +00:00
|
|
|
inject: ['projectPath'],
|
2021-08-12 12:11:05 +00:00
|
|
|
props: {
|
|
|
|
selectedLabels: {
|
|
|
|
type: Array,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
allowMultiselect: {
|
|
|
|
type: Boolean,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
},
|
2021-06-02 15:09:59 +00:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
searchKey: '',
|
2021-08-12 12:11:05 +00:00
|
|
|
labels: [],
|
|
|
|
localSelectedLabels: [...this.selectedLabels],
|
2021-06-02 15:09:59 +00:00
|
|
|
};
|
|
|
|
},
|
2021-08-12 12:11:05 +00:00
|
|
|
apollo: {
|
|
|
|
labels: {
|
|
|
|
query: projectLabelsQuery,
|
|
|
|
variables() {
|
|
|
|
return {
|
|
|
|
fullPath: this.projectPath,
|
|
|
|
searchTerm: this.searchKey,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
skip() {
|
|
|
|
return this.searchKey.length === 1;
|
|
|
|
},
|
|
|
|
update: (data) => data.workspace?.labels?.nodes || [],
|
|
|
|
async result() {
|
|
|
|
if (this.$refs.searchInput) {
|
|
|
|
await this.$nextTick();
|
|
|
|
this.$refs.searchInput.focusInput();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
error() {
|
|
|
|
createFlash({ message: __('Error fetching labels.') });
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-06-02 15:09:59 +00:00
|
|
|
computed: {
|
2021-08-12 12:11:05 +00:00
|
|
|
labelsFetchInProgress() {
|
|
|
|
return this.$apollo.queries.labels.loading;
|
|
|
|
},
|
|
|
|
localSelectedLabelsIds() {
|
|
|
|
return this.localSelectedLabels.map((label) => label.id);
|
|
|
|
},
|
2021-06-02 15:09:59 +00:00
|
|
|
visibleLabels() {
|
|
|
|
if (this.searchKey) {
|
|
|
|
return fuzzaldrinPlus.filter(this.labels, this.searchKey, {
|
|
|
|
key: ['title'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return this.labels;
|
|
|
|
},
|
|
|
|
showNoMatchingResultsMessage() {
|
|
|
|
return Boolean(this.searchKey) && this.visibleLabels.length === 0;
|
|
|
|
},
|
|
|
|
},
|
2021-08-12 12:11:05 +00:00
|
|
|
created() {
|
|
|
|
this.debouncedSearchKeyUpdate = debounce(this.setSearchKey, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
|
|
|
|
},
|
|
|
|
beforeDestroy() {
|
2021-08-30 09:09:12 +00:00
|
|
|
this.$emit('setLabels', this.localSelectedLabels);
|
2021-08-12 12:11:05 +00:00
|
|
|
this.debouncedSearchKeyUpdate.cancel();
|
|
|
|
},
|
2021-06-02 15:09:59 +00:00
|
|
|
methods: {
|
|
|
|
isLabelSelected(label) {
|
2021-08-12 12:11:05 +00:00
|
|
|
return this.localSelectedLabelsIds.includes(getIdFromGraphQLId(label.id));
|
2021-06-02 15:09:59 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* This method scrolls item from dropdown into
|
|
|
|
* the view if it is off the viewable area of the
|
|
|
|
* container.
|
|
|
|
*/
|
|
|
|
scrollIntoViewIfNeeded() {
|
|
|
|
const highlightedLabel = this.$refs.labelsListContainer.querySelector('.is-focused');
|
|
|
|
|
|
|
|
if (highlightedLabel) {
|
|
|
|
const container = this.$refs.labelsListContainer.getBoundingClientRect();
|
|
|
|
const label = highlightedLabel.getBoundingClientRect();
|
|
|
|
|
|
|
|
if (label.bottom > container.bottom) {
|
|
|
|
this.$refs.labelsListContainer.scrollTop += label.bottom - container.bottom;
|
|
|
|
} else if (label.top < container.top) {
|
|
|
|
this.$refs.labelsListContainer.scrollTop -= container.top - label.top;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2021-08-12 12:11:05 +00:00
|
|
|
updateSelectedLabels(label) {
|
|
|
|
if (this.isLabelSelected(label)) {
|
|
|
|
this.localSelectedLabels = this.localSelectedLabels.filter(
|
|
|
|
({ id }) => id !== getIdFromGraphQLId(label.id),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.localSelectedLabels.push({
|
|
|
|
...label,
|
|
|
|
id: getIdFromGraphQLId(label.id),
|
|
|
|
});
|
|
|
|
}
|
2021-06-02 15:09:59 +00:00
|
|
|
},
|
|
|
|
handleLabelClick(label) {
|
2021-08-12 12:11:05 +00:00
|
|
|
this.updateSelectedLabels(label);
|
|
|
|
if (!this.allowMultiselect) {
|
2021-09-03 12:09:03 +00:00
|
|
|
this.$emit('closeDropdown', this.localSelectedLabels);
|
2021-08-12 12:11:05 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
setSearchKey(value) {
|
|
|
|
this.searchKey = value;
|
2021-06-02 15:09:59 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2021-09-03 12:09:03 +00:00
|
|
|
<gl-dropdown-form class="labels-select-contents-list js-labels-list">
|
|
|
|
<gl-search-box-by-type
|
|
|
|
ref="searchInput"
|
|
|
|
:value="searchKey"
|
|
|
|
:disabled="labelsFetchInProgress"
|
|
|
|
data-qa-selector="dropdown_input_field"
|
|
|
|
data-testid="dropdown-input-field"
|
|
|
|
@input="debouncedSearchKeyUpdate"
|
|
|
|
/>
|
|
|
|
<div ref="labelsListContainer" data-testid="dropdown-content">
|
2021-08-12 12:11:05 +00:00
|
|
|
<gl-loading-icon
|
|
|
|
v-if="labelsFetchInProgress"
|
|
|
|
class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full"
|
|
|
|
size="md"
|
|
|
|
/>
|
2021-09-03 12:09:03 +00:00
|
|
|
<template v-else>
|
|
|
|
<gl-dropdown-item
|
2021-09-07 12:11:26 +00:00
|
|
|
v-for="label in visibleLabels"
|
2021-08-12 12:11:05 +00:00
|
|
|
:key="label.id"
|
2021-09-07 12:11:26 +00:00
|
|
|
:is-checked="isLabelSelected(label)"
|
|
|
|
:is-check-centered="true"
|
|
|
|
:is-check-item="true"
|
2021-09-03 12:09:03 +00:00
|
|
|
data-testid="labels-list"
|
|
|
|
@click.native.capture.stop="handleLabelClick(label)"
|
|
|
|
>
|
2021-09-07 12:11:26 +00:00
|
|
|
<label-item :label="label" />
|
2021-09-03 12:09:03 +00:00
|
|
|
</gl-dropdown-item>
|
|
|
|
<gl-dropdown-item
|
2021-08-12 12:11:05 +00:00
|
|
|
v-show="showNoMatchingResultsMessage"
|
|
|
|
class="gl-p-3 gl-text-center"
|
|
|
|
data-testid="no-results"
|
|
|
|
>
|
|
|
|
{{ __('No matching results') }}
|
2021-09-03 12:09:03 +00:00
|
|
|
</gl-dropdown-item>
|
|
|
|
</template>
|
2021-06-02 15:09:59 +00:00
|
|
|
</div>
|
2021-09-03 12:09:03 +00:00
|
|
|
</gl-dropdown-form>
|
2021-06-02 15:09:59 +00:00
|
|
|
</template>
|